什么是RunTime
RunTime是一套比较底层的纯C语言API,属于一个C语言库,包含了很多底层C语言API.我们平时编写的OC代码,程序运行过程中,其实最终都是转成RunTime的C语言代码,RunTime算是OC的幕后工作者.
比如:
OC中的代码
[[Student alloc] init];
其底层实现实际是消息发送机制,使用runtime表示为:
objc_msgSend(objc_msgSend(“Student” , “alloc”), “init”)
RunTime在什么时候使用
使用RunTime实现OC无法实现的,不好实现的一些非常底层的操作:
- 在程序运行过程中,动态的创建一个类(比如KVO的底层实现)
- 在程序运行过程中,动态的为某个类添加属性或者方法,修改属性的值或者方法
-
遍历一个类的所有成员变量(属性)或者方法(比如实现归档和反归档操作,有一个类就对其进行一次这样的操作比较麻烦,我们就可以给NSObject对象添加一个类目,这样就可以很方便的实现一键归档和反归档)
接下来我们用具体的代码来看一下怎样在项目中使用RunTime
1 使用RunTime实现归档和反归档
首先给NSObject写一个类目,遵守NSCoding协议,然后实现归档和反归档的两个方法
<code class="objectivec"> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68);">#import <span class="hljs-title">"NSObject+Coding.h"</span></span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 需要导入RunTime的头文件</span> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68);">#import <span class="hljs-title"><objc/objc-runtime.h></span></span> <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@implementation</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">NSObject</span> (<span class="hljs-title" style="color: rgb(102, 0, 102);">Coding</span>)</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 遍历类中所有的实例变量,逐个进行归档,反归档</span> -(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)encodeWithCoder:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSCoder</span> *)aCoder { <span class="hljs-comment" style="color: rgb(136, 0, 0);">/*class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。*/</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 遍历实例变量</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> ivarCount = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>; Ivar *vars = class_copyIvarList(object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>), &ivarCount); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>; i < ivarCount; i++) { <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 获取实例变量名字</span> Ivar var = vars[i]; <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *varName = [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> stringWithUTF8String:ivar_getName(var)]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// KVC</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> value = [<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> valueForKey:varName]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 进行归档</span> [aCoder encodeObject:value forKey:varName]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 释放指针</span> free(vars); } -(instancetype)initWithCoder:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSCoder</span> *)aDecoder { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> = [<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> init]; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>) { <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 遍历实例变量链表,逐个反归档</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> ivarCount = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>; Ivar *vars = class_copyIvarList(object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>), &ivarCount); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>; i < ivarCount; i++) { Ivar var = vars[i]; <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *varName = [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> stringWithUTF8String:ivar_getName(var)]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 反归档</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> value = [aDecoder decodeObjectForKey:varName]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// KVC</span> [<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> setValue:value forKey:varName]; } free(vars); } <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@end</span></code>
我们在使用的时候,创建一个Student类,在类中声明自己需要的属性,在要使用此类的控制器中实现归档和反归档操作,控制器中具体代码如下:
<code class="objectivec">Student *stu1 = [[Student alloc] init]; stu1<span class="hljs-variable" style="color: rgb(102, 0, 102);">.name</span> = <span class="hljs-string" style="color: rgb(0, 136, 0);">@"zhangsan"</span>; stu1<span class="hljs-variable" style="color: rgb(102, 0, 102);">.age</span> = <span class="hljs-number" style="color: rgb(0, 102, 102);">23</span>; <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSData</span> *data = [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSKeyedArchiver</span> archivedDataWithRootObject:stu1]; Student *stu2 = [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSKeyedUnarchiver</span> unarchiveObjectWithData:data]; <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSLog</span>(<span class="hljs-string" style="color: rgb(0, 136, 0);">@"%@"</span>, stu2<span class="hljs-variable" style="color: rgb(102, 0, 102);">.name</span>);</code>
2 使用RunTime实现多播委托
2.1 什么是多播委托
简单的说是指允许创建方法的调用列表或者链表的能力.当多播委托调用的时候,列表中的方法均自动执行.
在IOS中我就以我们平常用的最多的delagate为例,普通的delegate只能是一对一的回调,无法做到一对多的回调。而多播委托正式对delegate的一种扩展和延伸,多了一个注册和取消注册的过程,任何需要回调的对象都必须先注册。
如何在IOS中实现多播委托?老外早就已经写好了,而且相当的好用。我最初接触IOS多播委托是我在研究XMPPframework的时候,而多播委托可以说是XMPPframework架构的核心之一。具体的类名就是GCDMulticastDelegate,从名字就可以看出,这是一个支持多线程的多播委托。那为什么要支持多线程呢?我的理解是多个回调有可能不是在同一个线程的,比如我注册回调的时候是在后台线程,但是你回调的时候却在UI线程,那就有可能出问题了。因此必须保证你注册的时候在哪个线程上注册的,那么回调的时候必须还是在那个线程上回调的。
2.2 多播委托的本质,消息转发
如果一个对象收到一条无法处理的消息,运行时系统会在抛出错误前,给该对象发送一条forwardInvocation:消息,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。
2.2 使用RunTim怎么实现多播委托
给NSObject添加一个类目,添加两个方法,一个增加代理,一个移除代理.
NSObject+MultiDelegate.h
<code class="objectivec"><span class="hljs-preprocessor" style="color: rgb(68, 68, 68);">#import <span class="hljs-title"><Foundation/Foundation.h></span></span> <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@interface</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">NSObject</span> (<span class="hljs-title" style="color: rgb(102, 0, 102);">MultiDelegate</span>)</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)addDelegate:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)delegate; - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)removeDelegate:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)delegate; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@end</span></code>
NSObject+MultiDelegate.m
<code class="objectivec"><span class="hljs-preprocessor" style="color: rgb(68, 68, 68);">#import <span class="hljs-title">"NSObject+MultiDelegate.h"</span></span> <span class="hljs-preprocessor" style="color: rgb(68, 68, 68);">#import <span class="hljs-title"><objc/objc-runtime.h></span></span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 将数组关联对象时用的KEY</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *<span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> kMultiDelegateKey = <span class="hljs-string" style="color: rgb(0, 136, 0);">@"multiDatagateKey"</span>; <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@implementation</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">NSObject</span> (<span class="hljs-title" style="color: rgb(102, 0, 102);">MultiDelegate</span>)</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 添加</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)addDelegate:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)delegate { <span class="hljs-comment" style="color: rgb(136, 0, 0);">/*我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上。不过由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。*/</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 设置代理数组,为对象关联对象</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> *delegateArray = objc_getAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(kMultiDelegateKey)); <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 若当前对象没有关联的数组,创建并设置</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (!delegateArray) { delegateArray = [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> new]; objc_setAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(kMultiDelegateKey), delegateArray, OBJC_ASSO<span class="hljs-built_in" style="color: rgb(102, 0, 102);">CIATION_RETAIN_NONATOMIC</span>); } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 添加到数组中</span> [delegateArray addObject:delegate]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 删除</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)removeDelegate:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)delegate { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> *delegateArray = objc_getAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(kMultiDelegateKey)); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (!delegateArray) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@throw</span> [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSException</span> exceptionWithName:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"MultiDelegate error"</span> reason:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"数组为空是不对的"</span> userInfo:<span class="hljs-literal" style="color: rgb(0, 102, 102);">nil</span>]; } [delegateArray removeObject:delegate]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 消息转发给代理数组中的元素</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)doNothing { } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 消息转发</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">/*消息转发机制使用从下面这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。*/</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 获取方法标识</span> - (<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMethodSignature</span> *)methodSignatureForSelector:(SEL)aSelector { <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 获取代理的数组</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> *delegateArray = objc_getAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(kMultiDelegateKey)); <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 变量数组</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> aDelegate <span class="hljs-keyword" style="color: rgb(0, 0, 136);">in</span> delegateArray) { <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 取出每个元素对应aSelector的方法标识</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMethodSignature</span> *sig = [aDelegate methodSignatureForSelector:aSelector]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 如果sig 不为空,返回</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (sig) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> sig; } } <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> [[<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> class] instanceMethodSignatureForSelector:<span class="hljs-keyword" style="color: rgb(0, 0, 136);">@selector</span>(doNothing)]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">/*运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。*/</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 消息转发给其他对象</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)forwardInvocation:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSInvocation</span> *)anInvocation { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> *delegateArray = objc_getAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(kMultiDelegateKey)); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> aDelegate <span class="hljs-keyword" style="color: rgb(0, 0, 136);">in</span> delegateArray) { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>), ^{ <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 异步转发消息</span> [anInvocation invokeWithTarget:aDelegate]; }); } }</code>
2.4 使用
引入头文件 #import "NSObject+MulticastDelegate.h"
<code class="objectivec">以Student类为例 <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 创建student对象</span> Student *stu = [Student new]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 添加代理为控制器</span> [stu addDelegate :<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 调用代理方法</span> [stu performSelector:<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSSelectorFromString</span>(<span class="hljs-string" style="color: rgb(0, 136, 0);">@"doSomething"</span>) withObject:<span class="hljs-literal" style="color: rgb(0, 102, 102);">nil</span>]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 实现代理方法</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)doSomething { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSLog</span>(<span class="hljs-string" style="color: rgb(0, 136, 0);">@"doNothing"</span>); }</code>
3 使用RunTime实现KVO
3.1 KVO的内部实现
当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。
3.2 使用RunTime自己实现KVO
首先创建 NSObject 的 Category,并在头文件中添加两个 API:
<code class="objectivec"> <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@interface</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">NSObject</span> (<span class="hljs-title" style="color: rgb(102, 0, 102);">KVO</span>)</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 添加观察者</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)addObserver:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)observer forKey:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *)key withBlock:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>(^)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>, <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>))block; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 移除观察者 </span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)removeObserver:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSObject</span> *)observer forKey:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *)key; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@end</span></code>
接下来实现方法:
1、检查对象的类有没有相应的 setter 方法。如果没有抛出异常;
2、检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;
3、检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;
4、添加这个观察者
<code class="objectivec"><span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@implementation</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">NSObject</span> (<span class="hljs-title" style="color: rgb(102, 0, 102);">KVO</span>)</span> - (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)addObserver:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)observer forKey:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *)key withBlock:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>(^)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>, <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>))block { <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 获取setterName</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *setName = setterName(key); SEL setSelector = <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSSelectorFromString</span>(setName); <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通过SEL获取方法</span> Method setMethod = class_getInstanceMethod(object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>), setSelector); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (!setMethod) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@throw</span> [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSException</span> exceptionWithName:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"KVO Error"</span> reason:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"若无setter方法,无法KVO"</span> userInfo:<span class="hljs-literal" style="color: rgb(0, 102, 102);">nil</span>]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 获取当前类</span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 判断是否已经创建衍生类</span> Class thisClass = object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>); <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *thisClassName = <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSStringFromClass</span>(thisClass); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (![thisClassName hasPrefix:KVOClassPrefix]) { thisClass = [<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> makeKVOClassWithOriginalClassName:thisClassName]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 改变类的标识</span> object_setClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, thisClass); } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 判断衍生类是否实现了setter方法</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (![<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> hasSelector:setSelector]) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">char</span> *setType = method_getTypeEncoding(setMethod); class_addMethod(object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>), setSelector, (IMP)setter, setType); } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 将observer 添加到观察者数组</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> *observers = objc_getAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(KVOServerAssociatedKey)); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (!observers) { observers = [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> new]; objc_setAssociatedObject(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(KVOServerAssociatedKey), observers, OBJC_ASSO<span class="hljs-built_in" style="color: rgb(102, 0, 102);">CIATION_RETAIN_NONATOMIC</span>); } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 创建观察者info类</span> KVObServerInfo *info = [[KVObServerInfo alloc] initWithObserver:observer forKey:key withBlock:block]; [observers addObject:info]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 重写setter方法,新的setter在调用原来的setter方法后,通知每个观察者(调用之前传入的block)</span> - <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> setter(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> objc_self, SEL cmd_p, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> newValue) { <span class="hljs-comment" style="color: rgb(136, 0, 0);">// setterName 转为name</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *setName = <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSStringFromSelector</span>(cmd_p); <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *key = nameWithSetterName(setName); <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通过kvc获取key对应的value</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> oldValue = [objc_self valueForKey:key]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 将setter消息转发给父类</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> objc_super selfSuper = { <span class="hljs-variable" style="color: rgb(102, 0, 102);">.receiver</span> = objc_self, <span class="hljs-variable" style="color: rgb(102, 0, 102);">.super_class</span> = class_getSuperclass(object_getClass(objc_self)) }; objc_msgSendSuper(&selfSuper, cmd_p, newValue); <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 调用block</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMutableArray</span> *observers = objc_getAssociatedObject(objc_self, (__bridge <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)(KVOServerAssociatedKey)); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">for</span> (KVObServerInfo *info <span class="hljs-keyword" style="color: rgb(0, 0, 136);">in</span> observers) { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">dispatch_async</span>(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>), ^{ <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> ([info<span class="hljs-variable" style="color: rgb(102, 0, 102);">.key</span> isEqualToString:key]) { info<span class="hljs-variable" style="color: rgb(102, 0, 102);">.block</span>(objc_self, key, oldValue, newValue); } }); } } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 从setterName转回name</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *nameWithSetterName(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *setName) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (setName<span class="hljs-variable" style="color: rgb(102, 0, 102);">.length</span> <= <span class="hljs-number" style="color: rgb(0, 102, 102);">4</span> || ![setName hasPrefix:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"set"</span>] || ![setName hasSuffix:<span class="hljs-string" style="color: rgb(0, 136, 0);">@":"</span>]) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@throw</span> [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSException</span> exceptionWithName:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"KVO Error"</span> reason:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"set方法not available"</span> userInfo:<span class="hljs-literal" style="color: rgb(0, 102, 102);">nil</span>]; } <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *Name = [setName substringWithRange:<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSMakeRange</span>(<span class="hljs-number" style="color: rgb(0, 102, 102);">3</span>, setName<span class="hljs-variable" style="color: rgb(102, 0, 102);">.length</span> - <span class="hljs-number" style="color: rgb(0, 102, 102);">4</span>)]; <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *firstCharacter = [Name substringToIndex:<span class="hljs-number" style="color: rgb(0, 102, 102);">1</span>]; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> [[firstCharacter lowercaseString] stringByAppendingString:[Name substringFromIndex:<span class="hljs-number" style="color: rgb(0, 102, 102);">1</span>]]; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 在衍生类中判断set方法是否存在</span> - (<span class="hljs-built_in" style="color: rgb(102, 0, 102);">BOOL</span>)hasSelector:(SEL)aSelector { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> mCount = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>; Method *methods = class_copyMethodList(object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>), &mCount); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>; i < mCount; i++) { Method method = methods[i]; SEL setSelector = method_getName(method); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (setSelector == aSelector) { free(methods); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102);">YES</span>; } } free(methods); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102);">NO</span>; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通过runTime创建类</span> - (Class)makeKVOClassWithOriginalClassName:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *)className { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *kvoClassName = [KVOClassPrefix stringByAppendingString:className]; Class KVOClass = <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSClassFromString</span>(kvoClassName); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (KVOClass) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> KVOClass; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// objc_allocateClassPair 创建类</span> KVOClass = objc_allocateClassPair(object_getClass(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>), kvoClassName<span class="hljs-variable" style="color: rgb(102, 0, 102);">.UTF8String</span>, <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>); <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> KVOClass; } <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通过key获取对应的setterName</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *setterName(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *key) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (key<span class="hljs-variable" style="color: rgb(102, 0, 102);">.length</span> == <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>) { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@throw</span> [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSException</span> exceptionWithName:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"KVO Error"</span> reason:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"没有对应的key"</span> userInfo:<span class="hljs-literal" style="color: rgb(0, 102, 102);">nil</span>]; } <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *firstCharacter = [key substringToIndex:<span class="hljs-number" style="color: rgb(0, 102, 102);">1</span>]; <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *Name = [[firstCharacter uppercaseString] stringByAppendingString:[key substringFromIndex:<span class="hljs-number" style="color: rgb(0, 102, 102);">1</span>]]; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> [<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> stringWithFormat:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"set%@:"</span>, Name]; }</code>
最后一步,把这个观察的相关信息存在 associatedObject 里。观察的相关信息(观察者,被观察的 key, 和传入的 block )封装在 KVObServerInfo 类里。
<code class="objectivec"><span class="hljs-comment" style="color: rgb(136, 0, 0);">// 创建一个用于存放观察者info的类</span> <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@interface</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">KVObServerInfo</span>: <span class="hljs-title" style="color: rgb(102, 0, 102);">NSObject</span></span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 观察者属性</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">weak</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> observer; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// key属性</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>) <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *key; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 回调block</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@property</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">nonatomic</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>) ObsverserBlock block; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@end</span> <span class="hljs-class"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">@implementation</span> <span class="hljs-title" style="color: rgb(102, 0, 102);">KVObServerInfo</span></span> <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 初始化方法</span> - (instancetype)initWithObserver:(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span>)observer forKey:(<span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *)key withBlock:(ObsverserBlock)block { <span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> = [<span class="hljs-keyword" style="color: rgb(0, 0, 136);">super</span> init]; <span class="hljs-keyword" style="color: rgb(0, 0, 136);">if</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>) { _observer = observer; _key = key; _block = block; } <span class="hljs-keyword" style="color: rgb(0, 0, 136);">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span>; } <span class="hljs-keyword" style="color: rgb(0, 0, 136);">@end</span></code>
3.3 使用
在控制器中引入头文件 #import "NSObject+KVO.h"
<code class="objectivec">创建Student的对象 Student *stu1 = [[Student alloc] init]; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 给对象添加观察者</span> [stu1 addObserver:<span class="hljs-keyword" style="color: rgb(0, 0, 136);">self</span> forKey:<span class="hljs-string" style="color: rgb(0, 136, 0);">@"name"</span> withBlock:^(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> observerObject, <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSString</span> *key, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> oldValue, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">id</span> newValue) { <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSLog</span>(<span class="hljs-string" style="color: rgb(0, 136, 0);">@"%@"</span>, oldValue); <span class="hljs-built_in" style="color: rgb(102, 0, 102);">NSLog</span>(<span class="hljs-string" style="color: rgb(0, 136, 0);">@"%@"</span>, newValue); }]; stu1<span class="hljs-variable" style="color: rgb(102, 0, 102);">.name</span> = <span class="hljs-string" style="color: rgb(0, 136, 0);">@"张三"</span>; stu1<span class="hljs-variable" style="color: rgb(102, 0, 102);">.name</span> = <span class="hljs-string" style="color: rgb(0, 136, 0);">@"李四"</span>;</code>
到此我们自己手动完成了KVO的实现。