Method Swizzling的本质
Method Swizzling本质上就是对IMP和SEL进行交换。也就是我们常说的“黑魔法”
Method Swizzling原理
Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
而且Method Swizzling也是__iOS__中AOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。
可以通过两张图来理解
上面图一中selector2原本对应着IMP2,但是为了更方便的实现特定业务需求,我们在图二中添加了selector3和IMP3,并且让selector2指向了IMP3,而selector3则指向了IMP2,这样就实现了“方法互换”。
在OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。
在每个类中都有一个Dispatch Table,这个Dispatch Table本质是将类中的SEL和IMP(可以理解为函数指针)进行对应。而我们的Method Swizzling就是对这个table进行了操作,让SEL对应另一个IMP。
使用method-swizzling存在的问题
method-swizzling使用过程中的一次性问题
所谓的一次性就是:mehod-swizzling写在load方法中,而load方法会主动调用多次,这样会导致方法的重复交换,使方法sel的指向又恢复成原来的imp的问题
如图所示:
针对这种情况,应采用单利的方式进行处理
子类没有实现,父类实现了
LGPerson中实现了personInstanceMethod,而LGStudent继承自LGPerson,没有实现personInstanceMethod,运行下面这段代码会出现什么问题?
通过LGStudent的分类LG实现方法交换
调用类
运行结果:
- 原因分析:
崩溃点在于[p personInstanceMethod];原因在于LGStudent的分类中进行了方法交换,将person中的imp交换成了LGStudent中的lg_studentIndtanceMethod,然后需要去LGPerson中找lg_studentInstanceMethod,但是LGPerson中没有此方法,找不到imp,就引起崩溃。
- 优化
子类没有实现,父类也没有实现
- 优化
Method Swizzling类簇
在我们项目开发过程中,经常因为NSArray数组越界或者NSDictionary的key或者value值为nil等问题导致的崩溃,对于这些问题苹果并不会报一个警告,而是直接崩溃,感觉苹果这样确实有点“太狠了”。
由此,我们可以根据上面所学,对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等类进行Method Swizzling,实现方式还是按照上面的例子来做。但是....你发现Method Swizzling根本就不起作用,代码也没写错啊,到底是什么鬼?
这是因为Method Swizzling对NSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。
所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。
代码示例
发现了吗,__NSArrayI才是NSArray真正的类,而NSMutableArray又不一样😂。我们可以通过runtime函数获取真正的类:objc_getClass("__NSArrayI");
下面我们列举一些常用的类簇的“真身”:
| 类 | “真身” |
|---|---|
| NSArray | __NSArrayI |
| NSMutableArray | __NSArrayM |
| NSDictionary | __NSDictionaryI |
| NSMutableDictionary | __NSDictionaryM |
其他的可以去搜索下
KVC
KVC概念
Key Value Coding 也即 KVC 是 iOS 开发中一个很重要的概念,中文翻译过来是 键值编码 ,关于这个概念的具体定义可以在 Apple 的官方文档处找到。
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties.
【译】KVC 是通过 NSKeyValueCoding 这个非正式协议启用的一种机制,而遵循了这个协议的对象就提供了对其属性的间接访问。
基本用法
- 直接对属性或者成员变量进行取值和赋值
- 针对集合属性,可以直接通过mutableArrayValueForKey对对象的集合属性进行操作更改
- 针对结构体,KVC也可以直接操作,但是操作时候需要将结构体转成NSValue类型
- 假如对象的属性也是对象,那么KVC可以通过keyPath来操作对象属性的属性
- 假如字典的key和一个对象的属性都一样,那么可以通过setValuesForKeysWithDictionary直接将字典的value赋值给对象相应的属性,同样,也可以通过dictionaryWithValuesForKeys将对象转换成字典
- 通过api可以拿到数组元素的长度,也可以对数组元素进行操作得到新的数组
- 聚合操作符
KVC 的底层实现
- KVC-赋值过程
根据上面官方文档得知:
- 先依次查询有没有相关的方法:set< Key>:、_set< Key>:、setIs< Key>: 找到直接进行调用赋值。
- 若没有相关方法时,会查看类方法accessInstanceVariablesDirectly是否为YES时进入下一步。否则进入步骤4
- 为YES时,可以直接访问成员变量的来进行赋值,依次寻找变量 _< key >、 _is< Key>、 < key>、 is< Key>。找到则直接赋值,否则进入下一步。
- 将会调用**setValue:forUndefinedKey:**方法进行抛出异常。可以自定义的实现为未找到的场景来避免抛出异常的行为。
- KVC-取值过程
valueForKey: 方法会在调用者传入 key 之后会在对象中按下列的步骤进行模式搜索:
- 以
get<Key>,<key>,is<Key>以及_<key>的顺序查找对象中是否有对应的方法。
- 如果找到了,将方法返回值带上跳转到第 5 步
- 如果没有找到,跳转到第 2 步
- 查找是否有
countOf<Key>和objectIn<Key>AtIndex:方法(对应于NSArray类定义的原始方法)以及<key>AtIndexes:方法(对应于NSArray方法objectsAtIndexes:)
- 如果找到其中的第一个(
countOf<Key>),再找到其他两个中的至少一个,则创建一个响应所有NSArray方法的代理集合对象,并返回该对象。(翻译过来就是要么是countOf<Key>+objectIn<Key>AtIndex:,要么是countOf<Key>+<key>AtIndexes:,要么是countOf<Key>+objectIn<Key>AtIndex:+<key>AtIndexes:) - 如果没有找到,跳转到第 3 步
- 查找名为
countOf<Key>,enumeratorOf<Key>和memberOf<Key>这三个方法(对应于NSSet类定义的原始方法)
- 如果找到这三个方法,则创建一个响应所有
NSSet方法的代理集合对象,并返回该对象 - 如果没有找到,跳转到第 4 步
- 判断类方法
accessInstanceVariablesDirectly结果
- 如果返回
YES,则以_<key>,_is<Key>,<key>,is<Key>的顺序查找成员变量,如果找到了,将成员变量带上跳转到第 5 步,如果没有找到则跳转到第 6 步 - 如果返回
NO,跳转到第 6 步
- 判断取出的属性值
- 如果属性值是对象,直接返回
- 如果属性值不是对象,但是可以转化为
NSNumber类型,则将属性值转化为NSNumber类型返回 - 如果属性值不是对象,也不能转化为
NSNumber类型,则将属性值转化为NSValue类型返回
- 调用
valueForUndefinedKey:。 默认情况下,这会引发一个异常,但是NSObject的子类可以提供特定于key的行为。
总结:
kvc的使用如下图所示(IOS13有些私有属性的异常情况)