1. KVO原理
KVO的实现原理相信大家都应该有所了解, 就是在对象A为属性B添加监听addObserver: forKeyPath: options: context:
的时候会自动为类C(就是对象A的类)创建一个派生类D(子类), 并且在类D中重写被监听属性的setter方法E. 并且会把你被监听对象的isa指针由原来的C指向D, 当你调用被监听属性的setter方法时, 方法E会被执行. (这里不懂的话可以拿出纸笔在纸上画出来, 更好理解)
2.实现KVO
根据上面的原理我们知道, 实际上KVO就是Runtime的应用:
①因为在调用addObserver:
的时候会动态添加一个类newC, 所以这里会动态创建一个类;
②动态添加类之后我们还需要重写被监听对象的setter方法, 我们还需要为newC动态添加添加一个method;
我们开始先来模仿系统实现KVO的方式.
// 添加监听
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
// 移除监听
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
// 监听值的变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
上面这三个方法是我们在使用KVO会用到的三个方法, 现在我们来尝试着自己写这三个方法, 创建一个NSObject的category
@interface NSObject (KVO)
// 添加监听
- (void)ls_addObserver:(NSObject *)observer forKey:(NSString *)key;
// 移除监听
- (void)ls_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
@interface NSObject (Observering)
// 监听值变化
- (void)observeNewValue:(NSObject *)newValue;
@end
先来看添加监听的方法, 这里的任务是①动态创建一个被监听类的派生类; ②为整个派生类添加被监听属性的setter方法;③修改被监听对象的isa指针, 使其指向派生类;
- (void)ls_addObserver:(NSObject *)observer forKey:(NSString *)key {
Class cls = [self class];
NSString* clsName = NSStringFromClass(cls);
NSString* setKey = [NSString stringWithFormat:@"set%@%@:", [key substringToIndex:1].uppercaseString, [key substringFromIndex:1]];
SEL setterSelector = NSSelectorFromString(setKey);
Method setterMethod = class_getInstanceMethod(cls, setterSelector);
const char* types = method_getTypeEncoding(setterMethod);
// 1.动态创建一个类
Class newClss = objc_allocateClassPair(cls, [NSString stringWithFormat:@"%@%@", kLsClassNamePrefix, clsName].UTF8String, 0);
// 2.添加方法, 需要自己写setMethod
class_addMethod(newClss, setterSelector, (IMP)setMethod, types);
objc_registerClassPair(newClss);
// 3.替换isa指针
object_setClass(self, newClss);
// 记录下对象的key和监听者
NSMutableArray* servers = objc_getAssociatedObject(self, kKey.UTF8String);
if (!servers) {
servers = [NSMutableArray arrayWithCapacity:0];
objc_setAssociatedObject(self, kKey.UTF8String, servers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
ObserverInfo* info = [[ObserverInfo alloc] initWithObserver:observer key:key];
[servers addObject:info];
}
正如上面说的, 这里是有三个任务,
// 这里是添加的替换方法
static void setMethod(id self, SEL _cmd, id newValue) {
NSString* setterName = NSStringFromSelector(_cmd);
NSString* getterName = [setterName substringFromIndex:4];
getterName = [NSString stringWithFormat:@"%@%@", [setterName substringWithRange:NSMakeRange(3, 1)].lowercaseString, getterName];
getterName = [getterName substringToIndex:getterName.length - 1];
// setter方法的任务有两个
// 1. 告诉所有的监听此属性的对象, 对象属性发生改变
NSMutableArray* observers = objc_getAssociatedObject(self, kKey.UTF8String);
for (ObserverInfo* info in observers) {
if ([info.key isEqualToString:getterName]) {
if ([info.observer respondsToSelector:@selector(observeNewValue:)]) {
[info.observer performSelector:@selector(observeNewValue:) withObject:newValue];
}
}
}
// 2. 调用父类的setter方法
struct objc_super superClass = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
// 这里直接调用objc_msgSendSuper会发生错误
void(*objc_msgSendSuperCasted)(void *, SEL, id) = (void*)objc_msgSendSuper;
objc_msgSendSuperCasted(&superClass, _cmd, newValue);
}
这里是添加的setter方法
// 移除监听的方法
- (void)ls_removeObserver:(NSObject *)observer forKey:(NSString *)key {
NSMutableArray* servers = objc_getAssociatedObject(self, kKey.UTF8String);
ObserverInfo* info = nil;
for (ObserverInfo* observerInfo in servers) {
if (observerInfo.observer == observer && observerInfo.key == key) {
info = observerInfo;
break;
}
}
[servers removeObject:info];
}
然后在对应的监听者中实现- (void)observeNewValue:(NSObject *)newValue;
就可以了.
当然本文旨在让大家明白整体的流程, 这里的流程只是简单的实现了KVO, 里面还有很多的流程判断和注意事项就不一一赘述了.
源码地址
https://github.com/autmaple/SimpleKVO
参考资料
http://tech.glowing.com/cn/implement-kvo/