KVO

Posted coolcold

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KVO相关的知识,希望对你有一定的参考价值。

官方文档地址:

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html

基本用法:

1. [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL]

** context作用,用来打标签在回调方法中做区分,应用场景是在如果父类和子类有同一个属性名,回调判断会复杂,如果是用context来区分会很方便,在内存中只能惟一的写一个,相当于一个静态值。而且查找更优越,如果判断类和属性列表的话需要查找他们的缓存列表,所以context的优点是嵌套少,性能好。

2. 改变值 self.person.name = @"person";

3. 响应回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{

NSLog(@"%@",change);
NSLog(@"downloadProgress == %@",self.person.downloadProgress);
}

4. 析构:

- (void)dealloc{

[self.student removeObserver:self forKeyPath:@"name"];

}

** 如果没有在dealloc中移除观察者,并且被观察对象是一个单例的换,再次打开界面重新给name复制时会闪退或者调用两次等异常。(所以一定要移除,至于是什么导致的崩溃,闭源源码无法看到,猜测是不断添加注册观察者,当系统需要给观察者复制是,原页面已经销毁,找不到原地址,导致野指针崩溃。),下面是官方文档:

技术图片

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {

    if([key isEqualToString:@"name"]){

        return YES;

    }

    return NO;

}

返回NO,则之前添加的观察者,不再回调,也可以选择观察一些特定的key.

如果这里返回NO,也可以使用在set方法里面添加手动观察方法的方式实现添加观察者:

- (void)setName:(NSString *)name{

    [self willChangeValueForKey:@"name"];

    _name = name;

    [self didChangeValueForKey:@"name"];

}

 

 

 

KVO的一个简单应用(为downloadProgress添加观察者,影响下载进度的影响因素包括总的大小和已经写入的大小):

如果是传统的做法需要同时监控两个值,但是使用下面的方法就更便捷一些:

 

 

 

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{

 

    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

 

    if ([key isEqualToString:@"downloadProgress"]) {

 

        NSArray *affectingKeys = @[@"totalData", @"writtenData"];

 

        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];

 

    }

 

    return keyPaths;

 

}

 

 

上面的代码的主要作用是,当观察的值为downloadProgress时,添加连个影响因子totalData,writtenData,这样当这两个值改变的时候也会回调观察者方法。(联动观察 )

或者是把对象当做观察对象时,对象的属性改变也会调用观察者回调方法。

 

集合类观察:

如果采用上面的方式观察数组时,如果给数组添加元素,会发现不会调用观察者回调。如果要了解这个原因,就需要了解KVC的内容。(因为集合和简单变量的取值和赋值过程是不一样的)

[[self.student mutableArrayValueForKey:@"dataArray"] addObject:@"hello"];(满足KVC的设置流程)


这样就可以了,必须先初始化数组,接下来分析下原因:

 

根据之前KVC的官方文档中介绍。self.student是有可能获取不到值的,因为有_key,isKey,key这种变量的存在,赋值的时候不一定会复制到key中,有可能是_key中,所以self.student中不一定有值,所以获取不到。这样的话就需要直接避免这种情况,直接用KVC获取dataArray,这样可以规避KVC查找值导致的问题。

 

 

KVO底层原理:

首先验证下是否是观察的set方法:

以上是关于KVO的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发底层之KVO探索上 - 17

iOS开发底层之KVO探索上 - 17

KVO底层实现原理,仿写KVO

iOS底层探索之KVO—FBKVOController分析

通过子类实现KVO,浅析KVO底层原理

iOS底层探索之KVO—KVO简介