这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战
1. Method Swizzling的坑与应用
1.1 method-swizzling 是什么?
Method-Swizzling实际就是更换方法所对应的实现函数,其主要作用是在运行时将一个方法的实现替换成另一个方法的实现,这就是我们常说的iOS黑魔法。method-swizzling最常用的应用是防止数组、字典等越界崩溃。
下面的代码是否会无限递归呢?答案是不会的,因为交换方法后,lg_studentInstanceMethod实际上是通过[self personInstanceMethod]调用的,而 [self lg_studentInstanceMethod] 则是调用 personInstanceMethod,所以不会无限递归。
1.2 坑点
坑点1:method-swizzling使用过程中的确保执行一次
所谓的一次性就是:mehod-swizzling写在load方法中,而load方法会主动调用多次,这样会导致方法的重复交换,无法确保是否交换成功。
如何解决这个问题呢?
解决方案
可以通过单例设计原则,在OC中可以通过dispatch_once实现单例,这样就可以使方法交换只执行一次。
坑点2:子类没有实现,父类实现了
如果交换的方法在子类没有实现,在父类里面实现了,那么交换的时候会不会报错呢?
运行后发现报错了。这是因为,personInstanceMethod已经被交换成了lg_studentInstanceMethod。而在lg_studentInstanceMethod里面调用了[self lg_studentInstanceMethod],而LGPerson里面是没有lg_studentInstanceMethod方法的,所以抱错。
为什么要有[self lg_studentInstanceMethod]呢?这是因为如果在满足一定条件下,那么就还会走原有的逻辑。比如数组越界,如果index < array.count, 那么就还走原有的逻辑。
解决方案
先通过class_addMethod尝试给自己添加要交换的方法,如果添加成功,即类中没有这个方法,则通过class_replaceMethod进行替换,如果添加失败则代表有这个方法,正常进行method_exchangeImplementations。
坑点3:父类子类都没有实现
如果父类子类都没有实现,那么会有什么问题呢?运行后发现死循环了。那么就说明没有调换成功。所以sel lg_studentInstanceMethod还是指向自己的imp。当调用personInstanceMethod之后,会进入lg_studentInstanceMethod, 之后 [self lg_studentInstanceMethod]就会无限循环调用自己,就导致了死循环。
解决方案
判断oriMethod是否为空,如果为空则添加swiMethod方法并且添加imp实现。
class_replaceMethod, addMethod, method_exchangeImplementations 源码
2.KVC
KVC的全称是Key-Value Coding,翻译成中文是 键值编码,键值编码是一种由NSKeyValueCoding非正式协议启用的机制,对象采用该协议来提供对其属性的间接访问。既可以通过一个字符串key来访问某个属性。这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问。
2.1 KVC 设值
一般来说,对对象属性赋值有2种方式。
- 直接通过setter方法赋值
- 通过KVC键值编码的相关API赋值
查看setValueForKey方法,发现其在Foundation里面。而Foundation框架是不开源的,那么接下来就去苹果官方文档查找。
Key-Value Coding Programming Guide
在这里看到setValueForKey方法的流程如下:
第一步: 按顺序查找名为
set<Key>:,_set<Key>或者setIs<Key>的setter 方法。如果找到就调用它。
- 只要实现任意一个,那么就会将
调用这个方法将属性的值设为传进来的值。如果存在多个,则调用顺序在前面的。- 如果没有则进入第二步
第二步:如果三个setter方法都没有实现,那么就检查
accessInstanceVariablesDirectly是否返回YES,是的话就则按顺序查找名为_<Key>、_is<Key>、<Key>或is<Key>的实例变量。否则就跳到第三步。
- 只要找到任意一个,那么就会对
实例变量进行赋值,如果存在多个,则赋值顺序在前面的。- 如果没有就进入第三步
第三步: 如果找不到
setter方法或实例变量,系统会执行该对象的setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常。
setValueForKey流程图
2.2 KVC 取值
kvc 取值方法是valueForKey:,方法流程如下:
第一步:首先按照
get<Key>,<key>,is<Key>,_<key>的方法顺序查找getter方法,
- 如果找到,则进入【第五步】
- 如果没有找到,则进入【第二步】
第二步:如果没有实现getter 方法,KVC会查找
countOf <Key>,objectIn<Key> AtIndex :和<key> AtIndexes :
- 如果找到
countOf <Key>和其他两个中的一个方法,创建一个响应所有NSArray方法的集合代理对象即NSKeyValueArray,并返回该对象。否则就会去第三步。- 代理对象随后将接收到的所有NSArray消息转换为
countOf<Key>,objectIn<Key> AtIndex:和<key>AtIndexes:消息的某种组合,发送消息给创建它的键值编码对象。如果原始对象也实现了一个名为get<Key>:range:的可选方法,代理对象也会在适当的时候使用该方法。实际上,代理对象与键值编码对象一起工作来使属性可以像NSArray一样使用,即使它不是NSArray。
第三步:如果没有实现普通的getter 方法 或者 array 的组合getter 方法,那么就会同时查找
countOf <Key>,enumeratorOf<Key>和memberOf<Key>这三个方法
- 如果这三个方法都找到了,创建一个集合代理对象来响应所有的NSSet方法并返回它。否则,继续步骤4
- 这个代理对象随后将它收到的任何NSSet消息转换成
countOf<Key>、enumeratorOf<Key>、memberOf<Key>的一些组合: 发送消息到创建它的对象。实际上,代理对象与键值编码对象一起工作来使属性可以像NSSet一样使用,即使它不是NSSet。
第四步:如果没有实现普通的getter 方法 或者 集合 的组合getter 方法,并且accessInstanceVariablesDirectly返回YES,那么就会按顺序寻找实例变量
_<key>,_is<Key>,<Key>, 或is<Key>
- 只要找到任意一个,那么就会取得这个实例变量的值并且去到第五步。如果存在
多个,则获得顺序在前面的实例变量的值。然后去第五步。- 否则就去第六步。
第五步:返回结果
- 如果搜索到的属性值是对象指针,只需返回结果。
- 如果该值是NSNumber支持的标量类型,将其存储在NSNumber实例中并返回该对象。
- 如果结果是NSNumber不支持的标量类型,将其转换为NSValue对象并返回该对象。
第六步:如果上面5步的方法均失败,系统会执行该对象的valueForUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常
valueForKey流程图
3.自定义KVC
看过了设值和取值流程,我们可以根据苹果官方文档提供的查找规则进行实现。
3.1自定义设值
- 判断key是否为空
- 查找是否有 setter方法
set<Key>:,_set<Key>,setIs<Key>,如果有则实现并返回。
- 判断accessInstanceVariablesDirectly的返回值是否为YES,可以则往下走,否则抛出异常。
- 查找自己的ivar列表中是否包含实例变量
_<key>,_is<Key>,<key>,is<Key>,找到就赋值。
- 如果都搜索不到,就抛出异常。
3.2 自定义取值
- 判断key非空
- 按顺序查找方法:
get<Key>、<key>、countOf<Key>、objectIn<Key>AtIndex
- 判断accessInstanceVariablesDirectly的返回值是否为YES,可以则往下走,否则抛出异常。
- 查找自己的ivar列表中是否包含实例变量
_<key>,_is<Key>,<key>,is<Key>,找到就取值。
- 抛出异常。