Objective-C KVO 中 runtime 探究
2017-04-27 00:00
309 查看
最近学习Runtime,顺便总结一下在Objective-C中KVO使用到的Runtime机制。
动态创建Class
修改对象的Class
动态添加方法
runtime方法调用
动态了创建一个类(objc_allocateClassPair/objc_registerClassPair),该类是我们需要监听的对象的类的子类,然后修改当前对象的Class(object_setClass)。
给类动态的添加方法(object_setClass),比如KVO监听name属相,一般的需要添加一个setName:方法,然后添加方法的实现,在方法实现中,调用父类也就是原始类的setName:方法进行属性的设置(objc_msgSend,亦可以使用objc_msgSendSuper)。
给observer的回调方法发送监听消息(objc_msgSend),最后需要重置当前对象的Class(object_setClass),确保再次监听还会执行同样的流程.
我使用的是xcode8,ENABLE_STRICT_OBJC_MSGSEND开关默认是打开的,需要进行方法的指针强转为对应的方法才能调用
也可以设置ENABLE_STRICT_OBJC_MSGSEND开关关闭,那么可以不额外的处理objc_msgSend强转的问题,如果是一个库项目,那么不建议手动关闭这个开关,这样不管是ENABLE_STRICT_OBJC_MSGSEND开关打开还是关闭都不会影响。
objc_allocateClassPair 为空处理
objc_allocateClassPair 调用可能返回Nil,这种情况在当前的场景第二次调用同一个类对象的
系统的KVO使用
故事还得从OC的KVO说起,一般的我们使用KVO类似的如下所示,创建一个对象,然后调用addObserver方法进行某个属性的监听,有意思的是,我们在创建对象处和调用了
addObserver方法处打断点,然后使用po命令打印对象的isa,发现了对象的isa指针在调用了
addObserver方法之后变了,明显滴,调用了
addObserver方法之后使用了runtime机制动态的修改了对象的isa指针。
KVO中runtime的几个概念
大家一定会很好奇,runtime是怎么实现了KVO,那好下面就慢慢的揭开谜底。先了解几个runtime的概念:动态创建Class
objc_allocateClassPair可以动态创建Class,
objc_registerClassPair进行注册动态创建的Class
修改对象的Class
object_setClass可以修改对象的Class,也就是修改了isa指针指向的Class对象
动态添加方法
class_addMethod可以给类添加方法
runtime方法调用
objc_msgSend和
objc_msgSendSuper是OC消息发送机制的底层实现
KVO的实现步骤解析
好了,掌握了这几个runtime的概念,以及一开始看到了对象isa指针的变化,我们大概可以猜测KVO的实现了,大概需要以下几个步骤:动态了创建一个类(objc_allocateClassPair/objc_registerClassPair),该类是我们需要监听的对象的类的子类,然后修改当前对象的Class(object_setClass)。
给类动态的添加方法(object_setClass),比如KVO监听name属相,一般的需要添加一个setName:方法,然后添加方法的实现,在方法实现中,调用父类也就是原始类的setName:方法进行属性的设置(objc_msgSend,亦可以使用objc_msgSendSuper)。
给observer的回调方法发送监听消息(objc_msgSend),最后需要重置当前对象的Class(object_setClass),确保再次监听还会执行同样的流程.
自定义的KVO实现
系统的KVOaddObserver方法是NSObject对象的一个类别中
NSObject(NSKeyValueObserverRegistration)定义的方法,所以我们也创建一个NSObject的类别
NSObject (YTT_KVO),添加一个类似的监听方法。
创建KVO使用的类别
@interface NSObject (YTT_KVO) - (void)ytt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; @end
类别方法的实现
类别的监听方法,做的事情主要是保存方法参数,动态创建之类以及设置对象的Class到子类,动态的添加子类的方法。- (void)ytt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { // 保存keypath objc_setAssociatedObject(self, "keyPath", keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC); objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 获取当前类 Class selfClass = self.class; // 动态创建KVO类 const char * className = NSStringFromClass(selfClass).UTF8String; char kvoClassName[1000]; sprintf(kvoClassName, "%s%s", "YTT_KVO_", className); Class kvoClass = objc_allocateClassPair(selfClass, kvoClassName, 0); if (!kvoClass) { // Nil if the class could not be created (for example, the desired name is already in use). kvoClass = NSClassFromString([NSString stringWithUTF8String:kvoClassName]); } objc_registerClassPair(kvoClass); // 修改当前类指向为动态创建的KVO子类 object_setClass(self, kvoClass); // 动态添加一个方法:setXxx() SEL sel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", keyPath.capitalizedString]); class_addMethod(kvoClass, sel, (IMP)setValue, NULL); }
动态添加方法的实现
动态添加方法做的事情主要是创建和设置回调参数,包含新值和旧值,调父类的设置方法,给原始类设置值,给observer回调方法发送消息通知,告诉值改变了。void setValue(id self, SEL _cmd, id value) { // 保存当前的Class,重置Class使用 Class selfClass = [self class]; // 设置Class为原始Class object_setClass(self, [self superclass]); // 获取keyPath NSString* keyPath = objc_getAssociatedObject(self, "keyPath"); // KVO 回调参数 NSMutableDictionary* change = [NSMutableDictionary dictionary]; change[NSKeyValueChangeNewKey] = value; // 获取旧的值 SEL getSel = NSSelectorFromString([NSString stringWithFormat:@"%@", keyPath]); if ([self respondsToSelector:getSel]) { id ret = ((id(*)(id, SEL, id))objc_msgSend)(self, getSel, value); if (ret) { change[NSKeyValueChangeOldKey] = ret; } } // 给原始类设置数据 SEL setSel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", keyPath.capitalizedString]); if ([self respondsToSelector:setSel]) { ((void(*)(id, SEL, id))objc_msgSend)(self, setSel, value); } // 发送通知 id observer = objc_getAssociatedObject(self, "observer"); SEL observerSel = @selector(ytt_observeValueForKeyPath:ofObject:change:context:); if ([observer respondsToSelector:observerSel]) { ((void(*) (id, SEL, NSString*, id, id ,id))(void *)objc_msgSend)(observer, observerSel, keyPath, self, change, nil); } // 重置class指针,这样再次调用对象方法会走到这里面 object_setClass(self, selfClass); }
KVO通知的接收处理
在监听的对象中定义我们约定好的方法,类似Objective-C中的KVO进行处理- (void)ytt_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { id new = change[NSKeyValueChangeNewKey]; id old = change[NSKeyValueChangeOldKey]; NSLog(@"%@-%@", old, new); }
注意点
objc_msgSend 的使用注意我使用的是xcode8,ENABLE_STRICT_OBJC_MSGSEND开关默认是打开的,需要进行方法的指针强转为对应的方法才能调用
((void(*) (id, SEL, NSString*, id, id ,id))(void *)objc_msgSend)(observer, observerSel, keyPath, self, change, nil); // 这句代码的意思是把objc_msgSend这个任意类型的指针【(void *)】转换为一个返回值为void的函数指针【void(*)】,并且参数列表为【(id, SEL, NSString*, id, id ,id)】 // void (*) 声明一个函数指针,返回值是void类型 // (void *) 返回的是一个任意类型的指针 // 比如我们需要获取person的name属性,返回值是NSString*类型,那么objc_msgSend方法定义如下 id ret = ((NSString*(*)(id, SEL))objc_msgSend)(self.person, @selector(name));
也可以设置ENABLE_STRICT_OBJC_MSGSEND开关关闭,那么可以不额外的处理objc_msgSend强转的问题,如果是一个库项目,那么不建议手动关闭这个开关,这样不管是ENABLE_STRICT_OBJC_MSGSEND开关打开还是关闭都不会影响。
objc_allocateClassPair 为空处理
objc_allocateClassPair 调用可能返回Nil,这种情况在当前的场景第二次调用同一个类对象的
ytt_addObserver方法可能发生,这种情况,使用NSClassFromString方法创建Class。
Class kvoClass = objc_allocateClassPair(selfClass, kvoClassName, 0); if (!kvoClass) { // Nil if the class could not be created (for example, the desired name is already in use). kvoClass = NSClassFromString([NSString stringWithUTF8String:kvoClassName]); } objc_registerClassPair(kvoClass);
相关文章推荐
- Objective-C KVO 中 runtime 探究
- 深入runtime探究KVO
- Objective-C runtime 机制
- Objective-C Runtime初探:self super
- Objective-C Runtime 运行时之五:协议与分类
- Objective-C Runtime 运行时之六:拾遗
- 理解 Objective-C Runtime
- iOS runtime探究(四): 从runtiem开始实践Category添加属性与黑魔法method swizzling
- Objective-C语法之KVO的使用
- Objective-C runtime之消息转发机制(三)
- objective-c runtime
- Objective-C Runtime 运行时之一:类与对象
- Objective-C isa 指针 与 runtime 机制
- Objective-C Runtime 运行时之一:类与对象
- Runtime of Objective-C
- Objective-C Runtime 运行时之二:成员变量与属性
- Objective-C Runtime能做什么?
- iOS开发系列--Objective-C之KVC、KVO
- Objective-C Runtime 运行时之三:方法与消息
- Objective-C Runtime 运行时之一:类与对象