KVO内部实现原理

  • 动态创建一个子类,注册
  • 修改被观察者的类型,修改isa指针
  • 添加set方法
  • 动态绑定属性
#import 
@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 *)change context:(void *)context {
    NSLog(@"%@监听到%@属性的改变为%@", object, keyPath, change);
}

- (void)touchesBegan:(NSSet *)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拿不到真实的类型

参考资料

  • objc kvo简单探索
  • iOS开发-KVO的奥秘
  • KVO原理及自定义KVO
  • 手动实现KVO
  • 对于KVO,你真的了解么?

你可能感兴趣的:(KVO内部实现原理)