原文:http://www.cnblogs.com/lujianwenance/p/5800232.html
这是一个很蛋疼的过程,先说一下需求,列表页预加载更多(60%)。当我看到这个需求的时候感觉没什么,这个需求很正常。我还是先说下一下分享这个东西的目的,第一、就是让一些新人(主要是某个新人)了解一下,在开发一个新功能的时候,很少有情况是直接就把问题解决的,基本上都是经历了几次纠结的bug或者说是出乎意料的情况,这时候不要烦操,大脑不要混乱,要冷静的一步一步的分析这个bug或者意外的原因,发现并找到问题就成功了一半了。当然如果这个功能被你直接搞定了,只能说明你对这个需求很熟悉或者这个需求很easy。第二、简单的说一下ios黑魔法method-swizzling的使用。废话不多说,正文开始了。
一、分析问题
看到这个需求,我首先想到的是60%的时间点在哪,两个想法,一个是scrollview滑动到的位置,也就是offset;另一个是tableview滑动到的indepath。下面是分析的过程:1、这些列表页都有一个特点就是拥有上拉加载更多和下拉刷新,上拉加载更多的UI展示代码基本上是在tableview的tablefooterview中写的,60%加载数据跟footer没有太大的直接关系。2、scorllview的offset的值会根据你滑动的速度不同,得到的结果相差就比较大,所以在60%处预加载的偏差也会更大。3、预加载功能跟当前ViewController好像更合适,需不需要加载其实就是当前的ViewController来决定的。4、在tableview的cellForRow中判断当前显示到什么位置会更准确,滑动到60%就是滑动到了,没有什么争议。通过上面的分析,我决定使用在BaseViewController中确定预加载的时间点。(这只是我的理由,如果大神们看到有不合理的地方,请指正)
二、解决问题
找到了位置,下面就开始撸代码了,这段代码肯定是写在父类的,所以第一个想法就是父类里面实现tableview:cellforrowatindexpath:这个方法,然后所有的子类重写这个方法的时候先调用一下super的方法,还是感觉这样写改动的地方太多,而且让使用者很奇怪,感觉这是什么东西,怎么使用tableview的代理方法还需要调用super的?到这里,我想到了一个“很坑的”方法,method-swizzling。
method swizzling 被称为iOS 黑魔法,详细的介绍就不多说了,大家应该都看过交换ViewController的viewDidLoad方法。相了解更多可以看这个:http://nshipster.com/method-swizzling/ 中文:http://southpeak.github.io/blog/2014/11/06/objective-c-runtime-yun-xing-shi-zhi-si-:method-swizzling/ 也可以看这里:http://www.cocoachina.com/ios/20160121/15076.html 当然可以自己搜......
当按照网上大部分的教程中写的那样,在+load方法中添加我们的交换方法,然而运行的时候并没有效果,这时可能我们就会意识到,我们交换的方法并不是系统提供给我们的方法,所以load时交换没有任何效果。我将他加在了初始化方法中,这是成功的运行了。没有代码,好难受:
preLoadingScale是在加载更多的请求返回数据中计算下次需要自动加载的位置所占全部数据的比例,isDragingDown是用来判断从上向下滑动,感觉已经实现了,但经验告诉我们,这只是刚刚开始。
问题一、通过测试发现,第一次进入页面可以,第二次进入页面就没有效果了,第三次可以......很有规律的bug,这时充看一下代码,每次初始化一个对象交换一下方法,第一次交换,第二次交换......好像又交换回去了。对,确实是这样的,在交换方法里面加上dispatch_once:
再运行,OK了。
问题二、当第二次进入时另一个子类(代码是在父类中写的)的时候,没有作用。分析问题,交换的代码只执行一次,当第二次进入的是另一个子类的时候并没有交换当前类的方法,所以没有效果。解决问题,问题一的解决方案不合适,这时需要考虑这两种情况,这次修改的结果是:
重复上述的测试操作,没有问题了。
问题三、写完这个代码总感觉怪怪的,如果没有执行dealloc方法,方法还是没有被交换回来。所以想到了一个测试方法,进入一个子类中,没有问题,这个vc继续使用这个vc推出另一个子类,app crash了。分析问题,当进入第一个页面的时候交换了代理方法,继续进入另一个子类交换方法时,交换的是当前vc的tableview:cellforrowatindexpath:和之前那个页面的tableview:cellforrowatindexpath: ,所以在执行到第二个页面时,tableview的代理方法走到了第一个页面的代理方法中,导致了crash。解决问题,此时似乎问题越来越复杂了,从头到尾发现了这个多问题,都没有被解决,反而问题越来越多。其实不是这样的,这时候一定不能焦操,我们继续,从上面分析问题出现在没有及时的将方法交换回去,那么我们应该很容易想到这样写:
重复测试,没有问题了。
三、测试
每次做完一个功能,一定要自己先测试一下,因为写代码的时候是按照自己的“正常思路”在写,而BUG总是出现在一些“不正常”的地方,大多数人都会经历类似于这个过程的修改,当然不可否认,大神们根据自己的经验或者对知识的深入理解回跳过很多坑,但是坑总是存在的,总有“失误”的那一天。
四、总结
开头已经说了本文的目的,就是告诉新人(某个人)在遇到问题是不要烦、不要怕,而是要先复现bug,找到问题的原因 ,然后想解决的办法,不要自乱阵脚。(突然发现本文与题目好像没有多大的关系。。。。。。)还是需要总结一下method swizzling的使用点:
1、使用系统预制的方法,可以将交换方法写在+load方法中,但是当两个方法都是自己实现的方法时,+load中书写就没有效果了。
2、使用method swizzling 会影响整个类的所有子类,使用的时候需要特别小心,使用不当很容易造成“奇怪”的bug。比如在上面的例子中,所有的问题基本上都是使用了交换而没有恢复的情况。
3、不知道这样用是否合适,希望能看到的大神给出建议,谢谢。
====================华丽的分割线=============================
学习知识真的是温故而知新。当时使用了这个方案之后,由于展示这个页面的地方不是固定的,需要根据后台返回的数据来判断是需要展示在二级及二级以后的页面中还是展示在Tabbar的页面中,而且项目中存在很多以前自定义跳转动画的逻辑代码,导致viewController在调用viewWillAppear和viewWillDisappear的顺序上可能会乱序,所以放弃了这个方案(当然,后期经过考虑感觉写在footer中比这个方案更合适,这个合适的问题就不讨论了,下面主要是对这个方案的完善做分析),在过去两个月的时间之后,今天有时间回头看了一下这个问题。在介绍这个问题之前先说一下应用的场景,需要实现的viewController有一个封装了tableview的父控制器,使用这个预加载功能的页面都是继承自这个viewController,所以网上大部分的介绍Method-swizzling的文章所说的在+load方法中实现是不行的(load中拿到的是当前类的方法,无法获得父类或者子类中的方法,无法实现交换)。最终的实现是,对象实例化之后,先对子类中添加自己的需要交换的方法,然后在交换,这样就解决了上面交换之后还得交换回来的问题,因为其实之前的方案是将子类中的方法和父类中的方法交换,影响到了父类,导致每次生成一个新的子类时都会出现问题。现在就剩下一个问题了,那就是将交换过的类进行记录(我是写了一个manager单例来管理),如果当前类已经交换过了就不需要再次交换。到这里这个问题就被完整的解决了。2016.11添加