美文网首页
KVO内部实现原理

KVO内部实现原理

作者: geekAppke | 来源:发表于2017-06-21 15:43 被阅读35次
    • 动态创建一个子类,注册
    • 修改被观察者的类型,修改isa指针
    • 添加set方法
    • 动态绑定属性
    #import <objc/runtime.h>
    @implementation NSObject (MGKVO)
    
    - (void)MG_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
    {
        NSString *oldClassName = NSStringFromClass([self class]);;
        NSString *newClassNmae = [@"MGKVO_" stringByAppendingString:oldClassName];
        const char *newName = newClassNmae.UTF8String;
        
        // 动态创建类
        Class MyClass = objc_allocateClassPair([self class], newName, 0);
        // 注册这个类
        objc_registerClassPair(MyClass);
        // 修改调用者的类型
        object_setClass(self, MyClass);
        
        // 重写set方法
        class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:");
        // --- 保存观察者
        objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    // 把隐含参数补齐,就可以接收name了
    // id self, SEL _cmd
    void setName(id self, SEL _cmd, NSString *name)
    {
        NSLog(@"setName---调用了----%@", name);
        // 消息机制
        // 保存当前类 MGKVO_Person
        Class MyClass = [self class];
        // 将self指向父类,准备调用父类方法
        object_setClass(self, class_getSuperclass([self class]));
        // 调用父类方法
        objc_msgSend(self, @selector(setName:), name);
        // 通知观察者
        id observer = objc_getAssociatedObject(self, @"observer");
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self, nil, nil);
        // 改回子类
        object_setClass(self, MyClass);
    }
    @end
    

    使用

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        MGPerson *p = [[MGPerson alloc] init];
        p.name = @"lisa";
        _p = p;
    
    //    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        [p MG_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    }
    
    // 监听方法
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"%@监听到%@属性的改变为%@", object, keyPath, change);
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSString *randomStr = [NSString stringWithFormat:@"%d", arc4random()%10];
        self.p.name = randomStr;
        NSLog(@"name的值------%@", self.p.name);
    }
    
    • 类:方法调用
    • 对象:成员变量
    • 对象没有变化(内存地址),改变他的Class类型
    • 子类没有父类方法,只是在子类中找不到方法时才到父类里去找
      • 可以调用
      • 子类中没有,去父类找
      • 子类重写父类方法(直接给父类发消息的结构体较复杂)
        • OC里2个隐藏参数!重写子类方法时,要补齐
        • id类型的self,SEL类型的_cmd
    objc_msgSend(class_getSuperclass([self class]), @selector(setName:), name);
    // Use of undeclared identifier 'super'
    // objc_msgSendSuper(super, @selector(setName:), name);
    self 是子类
    一般用message,底层都会用到消息机制
    改回来,不然观察不到了
    
    调用父类安全,保证父类原有的行为
    直接消息转发,查找
    
    • category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)
    • extension一般用来隐藏类的私有信息
    • 对class进行伪装,利用getClass拿不到真实的类型

    参考资料

    相关文章

      网友评论

          本文标题:KVO内部实现原理

          本文链接:https://www.haomeiwen.com/subject/piaiqxtx.html