KVO 学习小结

163 阅读1分钟

1.KVO 使用

添加观察者 addObserver:forKeyPath:options:context
监听 observeValueForKeyPath:ofObject:change:context
移除观察者 removeObserver:forKeyPath  removeObserver:forKeyPath:context
    注:方法对应参数具体作用名称可参考官方文档不多赘述

2.KVO 触发模式

通过 + (BOOL) automaticallyNotifiesObserversForKey 返回值控制手动模式还是自动模式 默认为YES

3. KVO底层原理

1.创建一个子类
2.重写setter方法
3.设置isa指针

4.自定义KVO(通过runtime实现)

NSString *getValue(NSString *setter){
    NSRange  range = NSMakeRange(3, setter.length - 4);
    
    NSString *key = [setter substringWithRange:range];
    
    NSString *letter = [[key substringToIndex:1] lowercaseString];
    
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:letter];
    
    return key;
}

void setterMethodIMP(id self, SEL _cmd , NSString *newVaule){

    //第一种方式 改变父类属性
        struct objc_super superClass = {
            self,
            class_getSuperclass([self class])
        };
        //调用父类方法
        objc_msgSendSuper(&superClass, _cmd, newVaule);
    
     // 另一种 改变父类属性
        Class class = [ self class]; //拿到当前对象
        object_setClass(self,class_getSuperClass(class));
        objc_msgSend(self, _cmd,newValue);
        
        最后 通知观察者后改回子类
        object_setClass(self,class);
        //注:选其中一种即可

    //获取观察者
    id  observer = objc_getAssociatedObject(self, (__bridge const void*)@"objc");
    
    //通知发生改变
    NSString *methodName =  NSStringFromSelector(_cmd);
    //setName
    NSString *key = getValue(methodName);
    
    if (observer) {
        objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:),key,self,@{key:newVaule},nil);
    }

}

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

    // 注册类
    NSString *oldName = NSStringFromClass([self class]);
    NSString *newName = [NSString stringWithFormat:@"customKVO_%@",oldName];
    
    Class customClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
    
    //设置isa 指针 指向
    object_setClass(self, customClass);
    
    
    //重写setName方法
    
    NSString *methodName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];
    
    SEL sel = NSSelectorFromString(methodName);
    
    //参数
    class  给哪个类添加方法
    sel 方法名称
    imp 方法实现(函数指针)
    type  返回值类型

    class_addMethod(customClass, sel, (IMP)setterMethodIMP, "v@:@");
    
    //关联对象
    objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_ASSIGN);

}