一内容概述
本文主要分析kvo的底层原理,以及如何自己实现简易kvo。kvo的官方解释:Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.
二详细步骤
1.ios系统自带kvo实现原理
A.如何使用系统kvo
需求是要控制器要观察属性myBoy中属性name的变化,myBoy为boy类的对象,实现方式如下:
// 添加观察 myBoy添加控制器也就是下面的self为观察者,观察的key为name
[self.myBoy addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];
//观察回调方法 当myBoy通过.name或者setValue:forKey:方法改变name值时就会调用下面的方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"change:%@",change);
}
B.原理说明
1.当myBoy调用addObserver这个方法之后,系统会创建一个boy的派生类:NSKVONotifying_boy,在该子类中重写观察的属性(name)的set方法,然后会把当前对象myBoy的isa指针指向这个子类。注意虽然当前对象的isa指针变了,但是当前对象在内存中没有变,并没有生成子类的对象。
2.当myBoy的name属性发生变化的时候,会调用子类对象的set方法,在这个方法中,会先调用willChangeValueForKey:方法,然后调用父类(boy类)的set方法设置属性值,最后调用didChangeValueForKey:方法,这个时候就会调用观察者的observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context方法,告诉观察者观察的属性改变了。
3.手动调用系统kvo,首先要完成前面1、2代码,然后在boy类种重写方法:
//设置手动或者自动开启KVO 默认为YES 如果设置为NO 需要手动调用
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
return YES;
}
然后在需要触发通知的地方调用以下方法,也就是说即使不改变myBoy种name的值,只要调用以下两个方法也可以实现KVO通知:
[self.myBoy willChangeValueForKey:@"name"];
[self.myBoy didChangeValueForKey:@"name"];
2.实现简易kvo,我们在上面的需求上实现一个简易kvo
A. 实现逻辑为下面三个步骤:
a>创建子类
b>为子类添加该属性的set方法
c>将myBoy的isa指针指向子类
B. 具体实现代码
@implementation NSObject (kvo)
-(void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
// a创建子类 需要在子类的set方法中实现通知观察者observer
NSString*subname = [NSString stringWithFormat:@"JK_%@",NSStringFromClass([self class])] ;
Class newclass = NSClassFromString(subname);
if (!newclass)
{
newclass = objc_allocateClassPair([self class], subname.UTF8String, 0);
}
objc_registerClassPair(newclass);
// b为子类添加该属性的set方法 注意oc的方法实际上会变成调用函数 该函数的前两个参数必须是id self, SEL _cmd
// 最后一个参数表示该函数的参数 v表示void @表示对象 :表示SEL
class_addMethod(newclass, @selector(setName:), (IMP)setName, @"v@:@".UTF8String);
// cisa指针指向新子类
object_setClass(self, newclass);
// 把观察者关联到自己身上 用assign的方式 避免循环引用
objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);
}
// 实现子类的set方法
void setName(id self, SEL _cmd, NSString* name)
{
Class subclass = [self class];
Class superclass = class_getSuperclass(subclass);
object_setClass(self, superclass);
// 调用本身的set方法设置值
objc_msgSend(self, _cmd, name);
object_setClass(self, subclass);
//通知观察者
id observer = objc_getAssociatedObject(self, @"observer");
if (observer) { //当观察者被销毁后不会崩溃
[observer observeValueForKeyPath:NSStringFromSelector(_cmd) ofObject:self change:@{@"new":name} context:nil];
}
}
调用以下方法添加观察者,就可以如同系统kvo方法一样观察到属性的变化了:
-(void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
备注
kvo官方地址:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
本文demon地址:
https://github.com/songjk/ioskvo.git
网友评论