自己动手实现简单KVO

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/

你可能感兴趣的:(自己动手实现简单KVO)