iOS KVO机制

作者: 水煮杰尼龟 | 来源:发表于2020-09-29 15:48 被阅读0次

       来自官方文档的使用步骤

    step
    • 调用addObserver:forKeyPath:options:context: 方法来注册观察者
    • 在观察者内实现observeValueForKeyPath:ofObject:change:context:可以接收到属性发生变化的通知
    • 当观察者不再需要接收消息时,需要调用removeObserver:forKeyPath:移除监听,至少应在观察者释放之前调用。
    我们先看一下自动触发KVO

    创一个Human类,加上name,age属性,并监听,实现observeValueForKeyPath:ofObject:change:context ,修改一下值.

    self.human.name = @"lili";
    self.human.age = 19;
    [self.human addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    [self.human addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.human.name = [self.human.name isEqual:@"lili"]?@"lala":@"lili";
        self.human.age = self.human.age==19?20:19;
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        if ([keyPath isEqualToString:@"name"]) {
            NSLog(@"observeValueForKeyPath -- %@ -- %@ -- %@",keyPath,object,self.human.name);
        }else if ([keyPath isEqualToString:@"age"]){
            NSLog(@"observeValueForKeyPath -- %@ -- %@ -- %ld",keyPath,object,self.human.age);
        }
    }
    

    可以看到打印结果

    observeValueForKeyPath -- name -- <Human: 0x600001462ce0> -- lala
    observeValueForKeyPath -- age -- <Human: 0x600001462ce0> -- 20

    手动触发KVO

    首先在automaticallyNotifiesObserversForKey禁掉name的触发

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
        if ([theKey isEqualToString:@"name"]) {
            return NO;
        }
        return [super automaticallyNotifiesObserversForKey:theKey];
    }
    

    这里就需要用到下面2个方法了

    willChangeValueForKey:
    didChangeValueForKey:

    -(void)updateName:(NSString *)name{
       // _name = name;
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
    

    当我调用[self.human updateName:@"lala"]; ,一样的会收到name变化的通知

    observeValueForKeyPath -- name -- <Human: 0x600002bef5a0> -- lala

    那么KVC是怎么实现的呢,苹果的文档这么说的
    kvo
    意思是KVO是基于isa-swizzling实现的,在注册观察者的时候,会修改观察对象的isa指向。不要使用isa指针来判断类的关系,而应该使用class方法。
    接下来我们就基于这个来从代码看看KVO的实现。
    NSLog(@"object_getClass -- %@ --%@",object_getClass(self.human),[self.human class]);
        NSLog(@"address -- %p -- %p",object_getClass(self.human),[self.human class]);
        NSLog(@"%@",[self getMethods:object_getClass(self.human)]);
        [self.human addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        [self.human addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        NSLog(@"addObserver ----------------");
        NSLog(@"object_getClass -- %@ --%@",object_getClass(self.human),[self.human class]);
        NSLog(@"address -- %p -- %p",object_getClass(self.human),[self.human class]);
        NSLog(@"%@",[self getMethods:object_getClass(self.human)]);
        
        [self.human removeObserver:self forKeyPath:@"name"];
        [self.human removeObserver:self forKeyPath:@"age"];
        NSLog(@"removeObserver ----------------");
        NSLog(@"object_getClass -- %@ --%@",object_getClass(self.human),[self.human class]);
        NSLog(@"address -- %p -- %p",object_getClass(self.human),[self.human class]);
        NSLog(@"%@",[self getMethods:object_getClass(self.human)]);
    /// 获取class的方法
    -(NSArray *)getMethods:(Class)cls{
        NSMutableArray *array = [NSMutableArray array];
        unsigned int methodCount = 0;
        Method * methodList = class_copyMethodList(cls, &methodCount);
        unsigned int i;
        for(i = 0; i < methodCount; i++) {
            [array addObject:NSStringFromSelector(method_getName(methodList[i]))];
        }
        free(methodList);
        return array;
    }
    

    可以看到打印结果如下

    [5779:346617] object_getClass -- Human --Human
    [5779:346617] address -- 0x103de0818 -- 0x103de0818
    [5779:346617] (
    "updateName:",
    name,
    ".cxx_destruct",
    "setName:",
    "setAge:",
    age
    )
    [5779:346617] addObserver ----------------
    [5779:346617] object_getClass -- NSKVONotifying_Human --Human
    [5779:346617] address -- 0x6000015b4480 -- 0x103de0818
    [5779:346617] (
    "setName:",
    "setAge:",
    class,
    dealloc,
    "_isKVOA"
    )
    [5779:346617] removeObserver ----------------
    [5779:346617] object_getClass -- Human --Human
    [5779:346617] address -- 0x103de0818 -- 0x103de0818
    [5779:346617] (
    "updateName:",
    name,
    ".cxx_destruct",
    "setName:",
    "setAge:",
    age
    )

    如文档所说 isa确实发生了变化Human(0x103de0818)-> NSKVONotifying_Human(0x6000015b4480)-> Human(0x103de0818),
    可以看出在addObserver ,会将self.humanisa指向NSKVONotifying_Human, 根据打印结果我们可以看到NSKVONotifying_Human"setAge:","setName:",class,dealloc,"_isKVOA"这些方法。

    那么可能就会想到一个问题,添加监听之后,isa已经改变,而NSKVONotifying_Human里面却没有了updateName等其他方法,这时候调用不会出错吗?

    但是实际情况是不会出错的。
    我们来打印一下NSKVONotifying_Human的父类

    NSLog(@"-- super %@",class_getSuperclass(object_getClass(self.human)));
    

    [5921:361806] -- super Human

    好家伙,竟然是Human。那么这样就没问题了,可以去父类找到方法实现了。

    所以这里明白了NSKVONotifying_Human继承了Human ,并重写/增加了些方法。
    • 重写set方法,来支持KVO
    • 重写class方法,假装自己还是Human类(这里就说明了打印[self.human class]按道理应该是打印isa指向NSKVONotifying_Human,而实际是一直都没有变化,还是Human
    • 重写dealloc方法,做一些实例销毁时的清理工作
    • 添加了_isKVOA方法,来说明自己是kvo类
    接下来重写一下willChangeValueForKeydidChangeValueForKey,来看看给出通知的时机。
    -(void)willChangeValueForKey:(NSString *)key{
        [super willChangeValueForKey:key];
        NSLog(@"willChangeValueForKey");
    }
    -(void)didChangeValueForKey:(NSString *)key{
        NSLog(@"didChangeValueForKey - 111111");
        [super didChangeValueForKey:key];
        NSLog(@"didChangeValueForKey - 222222");
    }
    

    observeValueForKeyPath打上断点

    kvo

    [6337:400843] willChangeValueForKey
    [6337:400843] didChangeValueForKey - 111111
    [6337:400843] observeValueForKeyPath -- name -- <Human: 0x600003c48900> -- lala
    [6337:400843] didChangeValueForKey - 222222

    可以看出didChangeValueForKey内会给出通知

    以上就是对KVO的一些分析

    end

    相关文章

      网友评论

        本文标题:iOS KVO机制

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