KVO和 关联对象

35 阅读4分钟

KVO的本质

以p1对象为例 p1对象的isa指针在经过KVO监听之后已经指向了NSKVONotifyin_Person类对象,

  • NSKVONotifyin_Person其实是Person的子类,其superclass指针是指向Person类对象
  • NSKVONotifyin_Person是runtime在运行时生成的。
  • p1对象在调用setage方法的时候,会根据p1的isa找到NSKVONotifyin_Person,在NSKVONotifyin_Person中找setage的方法及实现。

NSKVONotifyin_Person中的setage方法中其实调用了 Fundation框架中C语言函数 _NSsetIntValueAndNotify,_NSsetIntValueAndNotify内部做的操作相当于,

  • 首先调用willChangeValueForKey 将要改变方法,
  • 之后调用父类的setage方法对成员变量赋值,
  • 最后调用didChangeValueForKey已经改变方法。
  • didChangeValueForKey中会调用监听器的监听方法,最终来到监听者的observeValueForKeyPath方法中。
  • 事实上foundtion内部还有其他函数比如: _NSSetBoolValueAndNotify、_NSSetCharValueAndNotify、_NSSetFloatValueAndNotify、_NSSetLongValueAndNotify等等函数

image.png

窥探NSKVONotifyin_Person类的内部

image.png

重写class方法是为了隐藏NSKVONotifyin_Person apple不希望将NSKVONotifyin_Person类暴露出来,并且不希望我们知道NSKVONotifyin_Person内部实现,所以在内部重写了class类,直接返回Person类

iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。

**如何手动触发KVO **

答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKey和didChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。

关联对象实现原理

-(void)setName:(NSString *)name { 
objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 
-(NSString *)name { 
return objc_getAssociatedObject(self, @"name"); 
}

参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。

参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在**objc_getAssociatedObject**中通过次key获得属性的值并返回。

参数三:** id value** : 关联的值,也就是set方法传入的值给属性去保存。

参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。 有以下几种

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { 
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象 
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性 
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性 
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性 
OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性 
};

  1. 移除所有关联对象
- (void)removeAssociatedObjects { 
// 移除所有关联对象 
objc_removeAssociatedObjects(self); 
}

实现关联对象技术的核心对象有

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation
    其中Map同我们平时使用的字典类似。通过key-value一一对应存值。

image.png

通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap,而AssociationsHashMap中存储着多个实例对象的ObjectAssociationMap,ObjectAssociationMap存储着多个次实例对象关联对象的key以及ObjcAssociation,ObjcAssociation中存储着关联对象的value和policy策略。

由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。

通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。

由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。