OC底层原理探索文档汇总
主要内容:
KVO的使用
KVO的底层实现
查阅KVO官方文档
key-value-observing(键值观察)
简单来说就是通过一个key来找到某个属性并监听其值的改变,KVC是实现KVO的基础,因为需要键值监听,KVO只能实现属性的监听,也就是有setter方法的监听,其他的比如成员变量是不行的。
KVO的简单使用
普通的属性设置
- 添加观察者
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
参数:
- observer:观察者,通常是自己,也可以是任何人
- keyPath:观察的内容,也就是属性名,这里写成字符串的形式(由此也可以知道KVO是基于KVC的)
- option:
- 响应类型,也就是在何种情况下会进行响应,决定了通知中的内容和发送时间。
- 常用的就是NSKeyValueObservingOptionNew,也就是更新属性值
- context:
- 表示上下文环境,在观察响应时传递一些额外的信息
- 一般可以用它来判断观察的属性是哪个
- context不能用nil填充,因为是viod *,所以需要用null
- context为了区分每一次观察的细节,不直接通过对象和keyPath来判断,这样比较麻烦,因为还需要进行大量对象的判断,这样写起来复杂又麻烦,用context可以性能好,而且可读性好
说明:
- 把person的nick属性添加观察者,观察者就是当前对象
- context用来传递额外信息
option:
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld:change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
- 在观察者中实现监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
参数:
keyPath:监听的属性
object:被监听者
change:表示数据修改的方式:
context:添加监听时的上下文参数
说明:
- 这个方法是一个是系统会调用的,当观察到属性发生变化时就自动调用
- 它是针对属性的监听后进行的响应
- NSKeyValueChangeKey用来设置观察到属性改变的方式,比如给数组插入数据,比如给一个属性更新一个数据
NSKeyValueChangeKey类型
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,//设值
NSKeyValueChangeInsertion = 2,//集合插入值
NSKeyValueChangeRemoval = 3,//集合移除值
NSKeyValueChangeReplacement = 4,//集合替换值
};
- 移除观察者
[a removeObserver:b forKeyPath:];
说明:
- 一般都在dealloc,也就是销毁该对象的时候
- 一定要移除的,不移除就会崩溃
为什么不移除观察者会崩溃:
- 如果没有移除,当被观察者的属性发生改变时,会通知查找观察者,而如果观察者已经被注销掉了,就无法通知到位,野指针异常了
属性依赖
如果我们想要监听一个属性,但是这个属性的变化不是直接修改的,而是通过其他几个属性的修改来影响的,这就是属性依赖。
也就是说我们通过监听其他值,响应目标值。两个值的观察变成一个值的观察,观察到两个值中只要有一个进行变化,即进行结果返回。
其他的都和普通的实现一样,重点在于keyPathsForValuesAffectingValueForKeyAPI的使用
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
//判断是真正要监听的那个属性
if ([key isEqualToString:@"downloadProgress"]) {
//添加keyPaths,这个数组里的每个属性发生变化都会响应观察
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
- 通过方法系统提供的类方法keyPathsForValuesAffectingValueForKey来实现的,最终返回keyPaths,而这个keyPaths里放的就是需要监听的那几个属性
- 当这个属性添加观察后,KVO会自动检测Set中的所有keyPaths
数组的设置
重点需要注意数组元素改变和数组本身的改变是不一样的,对于集合类型的操作一定要注意我们操作的方式,这里需要用到KVC来观察数组属性的改变。
代码:
// 5: 数组观察
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
//观察者数组的KVO,必须利用KVC的原理机制才可以观察到
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
说明:
- 对于数组的观察,需要使用KVC来进行设置才可以监听到
手动和自动设置开关
系统默认支持KVO的通知发送,同时系统也提供了一个入口,让我们可以手动去设置是否需要支持发送,如果对这个类关闭了自动开关后,还可以针对其中的某个属性进行手动开启。
手动设置开关可以让我们灵活的加上自己想要的判断条件
代码:
// 自动开关
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
// return YES;//自动
return NO;//手动,自己写setter
}
//这两个方法都是系统自动监听时要使用的方法,如果不自动监听,就需要我们自己加了。
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
说明:
- 重写这个方法,如果返回YES,就说明是自动的,如果返回NO,是手动的
- 如果需要对某个属性进行观察,可以在其setter方法中添加willChangeValueForKey和didChangeValueForKey方法
小结:
- KVO的使用都是三步曲,添加观察者,观察响应,移除观察者
- 我们可以自己控制是否需要进行观察
- 对于数组的观察需要使用KVC进行设值
- 通过属性依赖,可以对有依赖性质的属性进行观察
KVO的底层实现
核心就是新建了一个中间类,是当前类的子类,在setter方法中增加了两个方法,这样就可以进行观察了。
底层的实现过程
- 在添加观察后,系统会动态的生成一个新的中间类
- 中间类中有被观察的属性的setter方法
- setter方法包含三条件语句
- 1、通知观察者值即将改变[self willChangeValueForKey:@""];
- 2、修改值[super setValue: forKey:];
- 3、通知观察者值已经改变[self didChangeValueForKey@""];
注意:
- 观察的只是属性,不能观察成员变量,因为观察属性其实也观察的是setter方法
- 重新创建一个当前类的子类,这个子类的命名是这样的:NSKVONotifying_LGPerson
- 移除观察后,不会删除这个中间类,只要注册到内存中,就会一直存在
- 创建了中间类后,对象的isa会变成中间类,也就是我们观察的对象会指向中间类
- 这里值得注意的是此时这个对象通过class返回的仍然是原来的类
- 移除观察后还会指回之前的类