iOS底层原理之KVO分析(上)

什么是KVO?

KVO是Key-Value Observing的简写,称为键值观察,用来监听对象属性的变化markdown

KVO使用探究

咱们以IFPerson类为例研究KVO的使用。函数

自动触发KVO监听

  • 主要代码
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.person.name = @"kvo";
    NSLog(@"class = %@",object_getClass(self.person));
    //对person的name添加kvo监听
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    NSLog(@"class = %@",object_getClass(self.person));
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person.name = [NSString stringWithFormat:@"%@ +",self.person.name];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSLog(@" === %@",change);
    }
}

-(void)dealloc {
    //移除监听
    [self.person removeObserver:self forKeyPath:@"name"];
}
复制代码

运行代码点击屏幕效果以下spa

手动触发KVO监听

若是想要手动触发KVO监听,须要重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key函数,该函数默认返回YES,表示自动触发,返回NO,则表示手动触发。对于本文,咱们判断下key == name则手动触发,不然自动触发。修改代码以下3d

//在`IFPerson.m`中
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}
-(void)setName:(NSString *)name {
    [self willChangeValueForKey:name];
    _name = name;
    [self didChangeValueForKey:name];
}
复制代码

从上述代码中,咱们能够看到,要实现手动触发KVO,有两个步骤须要实现指针

  • 步骤一:重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key函数,根据须要返回NO
  • 步骤二:对于监听的对象属性发生改变时,须要在改变先后调用willChangeValueForKey:didChangeValueForKey:

KVO底层原理探究

从上述中咱们能够看到咱们能够根据KVO监听属性的变化,那么在底层是怎么实现的呢?code

KVO监听先后对象的变化

咱们在监听name先后获取personclass以及其指针地址看下是否有变化,代码以下orm

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.person.name = @"kvo";
    NSLog(@"class = %@ address = %p",object_getClass(self.person),self.person);
    //对person的name添加kvo监听
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    NSLog(@"class = %@ address = %p",object_getClass(self.person),self.person);
}
复制代码

打印结果以下从上图中咱们看到,self.personclass先后发生了改变,可是对象地址并无改变。咱们再经过获取isa来验证下。server

  • 监听以前
  • 监听以后
  • 监听先后总结:从上面分析可知,在进行KVO监听后,对象的class即isa指向发生了改变,由IFPerson变成了NSKVONotifying_IFPerson,可是对象的地址没有改变

NSKVONotifying_IFPerson是什么?

咱们知道了在进行KVO监听后class变成了NSKVONotifying_IFPerson,那么NSKVONotifying_IFPerson从何而来?下面咱们看下NSKVONotifying_IFPerson的父类是谁。对象

准备工做

因为superClass是隐藏属性,在外部没法访问,由于咱们定义一个结构体,并强转下类型来得到superClass,代码以下继承

struct cw_objc_class {
    Class _Nonnull isa;
    Class _Nullable super_class;
};

// 强转一下类型
    struct cw_objc_class *newClass = (__bridge struct cw_objc_class *)object_getClass(self.person);
    NSLog(@"superClass = %@",newClass->super_class);
复制代码

从打印结果来看NSKVONotifying_IFPerson父类居然是IFPerson

NSKVONotifying_IFPerson类实现了哪些方法?

既然runtime在进行KVO监听的时候派生了NSKVONotifying_IFPerson类,那么NSKVONotifying_IFPerson类跟IFPerson类实现方法有什么区别呢?咱们打印下监听先后两个类的实现方法区别

#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}
NSLog(@"监听以前方法:");
    [self printClassAllMethod:object_getClass(self.person)];
    
    //对person的name添加kvo监听
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    NSLog(@"监听以前方法:");
    [self printClassAllMethod:object_getClass(self.person)];
复制代码

打印结果以下从打印监听先后方法列表咱们能够看到NSKVONotifying_IFPerson类有以下变化

  • 重写了被监听属性的setter方法,如setName:
  • 修改了isa指针指向的地址,由原来的IFPerson指向了NSKVONotifying_IFPerson
  • 实现了dealloc方法
  • 增长了一个_isKOA标识符

isa指针什么时候从新指向IFPerson

既然isa指针在被监听后指向了NSKVONotifying_IFPerson类,那么什么时候从新指向IFPerson呢?答案是在移除监听的时候。为了验证,咱们在dealloc中打印下监听先后isa指针的变化。

KVO总结

  • KVO在监听属性后,runtime会根据对象的类型生成一个NSKVONotifying_XXX(XXX是原来的类)派生类,该派生类NSKVONotifying_XXX继承于原来的类
  • NSKVONotifying_XXX重写被监听属性的setter方法,可是赋值的时候经过重写的setter内部消息转发仍是经过原来类的setter方法来实现
  • KVO在移除监听时将isa指针指向地址由NSKVONotifying_XXX派生类从新指向原来类
相关文章
相关标签/搜索