KVO原理解析--自定义KVO

2018-12-06  本文已影响0人  rookiesss

kvo的用法就不再赘述。

[self.name addObserver:self
                 forKeyPath:@"name"
                    options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
                    context:nil];

-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSString *,id> *)change
                      context:(void *)context{
        NSLog(@"%@", change);
}

核心思想,以观察name属性为例:
重写name属性的setter方法,当name值改变时在setter方法中调用observeValueForKeyPath方法。

先说说看我自己的思路:
1.替换name属性的setter方法。

- (void)dy_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath 
               options:(NSKeyValueObservingOptions)options context:(nullable void *)context {

    NSString* setterMethodName = setterForGetter(keyPath);
    SEL setterSEL = NSSelectorFromString(setterMethodName);
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char* setterTypes = method_getTypeEncoding(setterMethod);
  
    //替换name的setter方法自己实现
    class_replaceMethod([self class], setterSEL, (IMP)dy_setter, setterTypes);
    //将观察者存到数组作为属性,方便后面使用
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge void *)@"observerArr")?: [NSMutableArray array];
    
    if (![observerArr containsObject:observer]) [observerArr insertObject:observer atIndex:0];
    objc_setAssociatedObject(self, (__bridge void *)@"observerArr", observerArr, OBJC_ASSOCIATION_RETAIN);
}

2.在name改变时通过重写name属性的setter方法调用observeValueForKeyPath方法。

static void dy_setter(id self, SEL _cmd, id newValue) {

    NSString* setterName = NSStringFromSelector(_cmd);
    NSString* key = getterForSetter(setterName);
    char *char_content = (char *)[[NSString stringWithFormat:@"_%@",key] cStringUsingEncoding:NSASCIIStringEncoding];
    Ivar ivar = class_getInstanceVariable([self class], char_content);
    //给name属性赋值
    object_setIvar(self, ivar, newValue);
    // 通知观察者, 值发生改变了
    id observerArr = objc_getAssociatedObject(self, (__bridge void *)@"observerArr");
    for (id observer in observerArr) {
        [observer observeValueForKeyPath:key ofObject:self change:@{key:newValue} context:nil];
    }
}

3.setterForGetter & getterForSetter。

#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString  * setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil; }
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> Key
static NSString * getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    
    return getter;
}

4.移除观察者.

如果Person中重写了setName方法这样做会导致Peroson中setName无效,在苹果的实现中,先创建了一个self的子类,在子类中重写 了setName方法,这样能保证Peroson中重写的setName有效。
代码类似,代码在这里

上一篇下一篇

猜你喜欢

热点阅读