在项目中谨慎为系统类添加分类!!!!!
Posted cchhers
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在项目中谨慎为系统类添加分类!!!!!相关的知识,希望对你有一定的参考价值。
结论:
1、坚决杜绝为系统类做方法交换(见到【class_replaceMethod】格杀勿论!)
2、为系统类添加分类时候,属性和方法名必须加上【世上独一无二】的前缀,避免冲突和混淆。
之所以让我对上述行为恨之入骨是因为,今天为了一个bug,我花了将近半天时间苦苦追寻原因。
我只是使用了一个简简单单的UIImagePickerController的拍照的功能界面,奇葩的现象是,点击快门按钮时---可以看见界面中的按钮发生了视觉上的响应,但是却没有功能上的响应(按道理,我这边按下按钮的时候,拍照就会完成输出图片数据)。
我的整个思考过程是这样的:
点击没功能反应?难道有谁把这个类中的响应方法重写了?
---寻找UIImagePickerController在整个项目中的出现,看有没有对它做分类,或者是子类化。结果是没有的!
那是不是关于UIImagePickerController这个类,随着ios的SDK的更新,我有些属性或者方法需要适配下?
---我用iOS10.2和iOS11.2和最新的iOS11.4都看了一遍,都有这个问题。难道从iOS10开始就要有些跟之前不一样的适配需要做?我翻看相关的适配博客,没有发现!
难道是我对事件响应链做了一些调整?导致事件被阻断?
---我回头看了一眼UIImagePickerController对象创建后使用的是模态出来的,一个简单的展示链,没有问题!
难道多线程问题?
---NONONO!我核对了下代码,整个过程都在主线程中,至于就算UIImagePickerController里面的处理上开了子线程,那也不归我们管,它暴露出来的API肯定是在主线程的。
那就见鬼了~但是,不对啊,就这个简单的UIImagePickerController,不至于啊!
---我应该是知道肯定是项目中的其他SDK的环境影响到了它,但是会是什么呢?为了更加确定我的这个想法,给自己继续追寻原因的信心,我新建demo,这块代码原样放入。卧槽,完美运行。
那行,我这样的话,我要一查到底!
---能够引起这个问题的全局原因,那么就是项目的配置数据有误,那么就是分类的原因。
项目的配置数据就是那些,最多就是在info文件中说明下使用相机的原因,方便获取用户的授权。因此我肯定,这块没有问题。
分类的话,我已经确定了没有UIImagePickerController的分类。那么肯定就是其他系统类的分类了。
首先,添加分类的不可控性体现在:
(1)如果在分类中重写类的方法,分类的重写优先级是最高的。
(2)如果系统对UIImagePickerController添加了一些分类(包括不暴露在API中的),刚好又与项目中对其的分类方法名重复,会后入为主的。
(3)另外分类是会在编译器就全部加上的,如果在分类中对类本身做的处理是会影响到类本身的。也就是说,如果对类中的方法做了方法转移的处理,那就无形中影响了。
于是我赶紧搜索方法转移的class_replaceMethod方法名有没有在项目中出现。果然,项目中对UIButton的分类中重写了+load类方法,在改方法中做了方法转移!
正如前面分析的,重写+load方法的优先级:分类中>子类中>类本身。
并且重写的是+load这个方法,完全可以做到悄无声息。
为了进一步验证就是这个原因,我直接将这个分类的实现方法注释掉,然后运行项目~【method_exchangeImplementations完美运作!!】
刚刚时候的是相当于反编译的方式把问题的根源找到了,现在我需要的是使用顺推的方法,把问题的出现原因梳理清楚。
通过查看UIButton的这个分类知道,它是将@selector(sendAction:to:forevent:)这个方法替换掉了。sendAction:to:forevent:方法中实际调用的是objc_setAssociatedObject,替换后的方法,在其中加了一个计时器,使得规定时间内,只能objc_setAssociatedObject调用一次。
这样的做法,应该是为了防止button高频按动而做的改动。
然而,UIImagePickerController功能界面中的快门按钮,实际上是在拍照功能时,按住快门键不放,可以实现高频连拍的功能(我试了下最多时999张),这样的话,就很好解释通了。虽然,按住快门键按钮不放是一个“长按”手势,但是其内部的实现肯定是高频的调用@selector(sendAction:to:forevent:)这个方法。说到这里,我得说明下,虽然长按手势和单点手势表面上的确是不一样的,但是其内部都调用了@selector(sendAction:to:forevent:)这个方法。因此,之前写button这个分类的目的虽然是防止用户高频的单击按钮,但是现在用户虽然不是高频的单击,而是长按,但是都调用的是@selector(sendAction:to:forevent:)这个方法。毕竟,当初为了防止用户高频单击,是替换掉了@selector(sendAction:to:forevent:)这个方法。因此,谜底揭开了,整个离奇的故事真相大白~
以上是关于在项目中谨慎为系统类添加分类!!!!!的主要内容,如果未能解决你的问题,请参考以下文章