iOS笔记 - KVO实现原理
KVO实现原理
1 – 代码示例
① 在 Person 中声明 age属性;在 ViewController.m中创建两个 Person实例对象,将其中一个添加观察者
// – Person.h
1 #import <Foundation/Foundation.h> 2 @interface Person : NSObject 3 4 @property(nonatomic,assign)int age; 5 6 @end
// – Person.m
1 #import "Person.h" 2 @implementation Person 3 @synthesize age = _age; 4 5 // 没有添加观察者的对象,点语法依旧调用原来的 setter方法 6 - (void)setAge:(int)age{ 7 _age = age; 8 } 9 10 - (int)age{ 11 12 return _age; 13 } 14 15 // 添加了观察者的对象,调用 setter方法的实现如下(这些都是在 runtime中动态生成) 16 // ----- 下面是伪代码,模拟 KVO实现流程 ----- 17 /* 18 19 // 在 setter方法中会执行 __NSSetInValueAndNotify函数,它是 C语言私有函数,不会有提示 20 - (void)setAge:(int)age{ 21 22 __NSSetInValueAndNotify(); 23 } 24 25 // __NSSetInValueAndNotify函数的作用 26 void __NSSetInValueAndNotify(){ 27 28 // 首先调用 willChangeValueForKey方法 29 [self willChangeValueForKey:@"age"]; 30 31 // 其实是开始初始化实例变量:直接调用父类的初始化方法 32 [super setAge:age]; 33 34 // 最后调用 didChangeValueForKey方法 35 [self didChangeValueForKey:@"age"]; 36 37 } 38 39 // 重点来了:didChangeValueForKey会拿到这个 observer,触发监听方法 40 - (void)didChangeValueForKey:(NSString *)key{ 41 42 // 触发监听 43 [observer observeValueForKeyPath:@"age" ofObject:self change:@{} context:nil] 44 } 45 46 **/ 47 48 // 重写这两个方法,验证监听方法是在 didChangeValueForKey中触发的 49 - (void)willChangeValueForKey:(NSString *)key{ 50 51 NSLog(@"willChangeValueForKey-begin"); 52 [super willChangeValueForKey:key]; 53 NSLog(@"willChangeValueForKey-end"); 54 } 55 56 -(void)didChangeValueForKey:(NSString *)key{ 57 58 NSLog(@"didChangeValueForKey-begin"); 59 [super didChangeValueForKey:key]; 60 NSLog(@"didChangeValueForKey-end"); 61 } 62 63 @end
// – ViewController.m
1 #import "ViewController.h" 2 #import <objc/runtime.h> 3 #import "Person.h" 4 @interface ViewController() 5 6 @end 7 8 @implementation ViewController 9 10 // 添加了观察者的对象,其内部会调用 setAge: class dealloc _isKVOA 11 // 我们在此把它们打印出来进行验证 12 -(void)printMethods:(Class)cls{ 13 14 // 记录函数个数 15 unsigned int count; 16 // 拿到函数列表 17 Method *methods = class_copyMethodList(cls, &count); 18 19 // 字符串:拼接将要遍历出的函数名 20 NSMutableString *methodsNames = [NSMutableString string]; 21 [methodsNames appendFormat:@"添加了观察的对象 %@所调用的方法有:\n",cls]; 22 23 // 遍历函数 24 for (int i = 0; i < count; i++) { 25 26 Method method = methods[i]; 27 NSString *methodName = NSStringFromSelector(method_getName(method)); 28 [methodsNames appendString:methodName]; 29 [methodsNames appendString:@" "];// 间隔 30 } 31 32 NSLog(@"%@",methodsNames); 33 34 // C语言函数,需要释放 35 free(methods); 36 } 37 38 39 // 其中,我们使用 lldb命令更好的展示 40 - (void)viewDidLoad { 41 [super viewDidLoad]; 42 self.view.backgroundColor = [UIColor cyanColor]; 43 44 45 Person *p1 = [Person new]; 46 // p p1->isa Person 47 p1.age = 100; 48 49 Person *p2 = [Person new]; 50 // p p2->isa Person 51 p2.age = 10000; 52 53 54 // 未添加观察者之前,点语法走的都是同一个 setter 55 NSLog(@"%p",[p1 methodForSelector:@selector(setAge:)]); // 0x10f760390 56 NSLog(@"%p",[p2 methodForSelector:@selector(setAge:)]); // 0x10f760390 57 // p (IMP)0x10f760390 [Person setAge:] 58 59 60 // p1添加观察者后,其 isa指针发生了变化,指向了一个新的类对象 61 [p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"传参"]; 62 // p p1->isa NSKVONotifying_Person 63 // 思考:新的类对象 NSKVONotifying_Person从哪里来,又是什么东东 64 // 其实 NSKVONotifying_Person是 Person的子类,它是运行时动态生成 65 66 // 遍历方法 67 [self printMethods:object_getClass(p1)]; 68 // 输出结果 setAge: class dealloc _isKVOA 69 70 // 注意 class 71 NSLog(@"%@",[p1 class]);// 输出 Person 72 // 我们知道添 p1加了观察者后,其类对象是 NSKVONotifying_Person,那为什么这里为什么打印的还是 Person 73 // 因为 NSKVONotifying_Person中重写了 class。要知道新产生的类对象是运行时动态生成的,苹果是不愿意将其暴露出来的 74 // 如何重写我们无法得知,但一定的是它最终会返回类对象 Person,而不是自己的 NSKVONotifying_Person 75 76 77 // 重新赋值 78 p1.age = 200; 79 p1.age = 300; 80 p2.age = 20000; 81 82 83 // p2调用的方法依旧是原来的 setter 84 NSLog(@"%p",[p2 methodForSelector:@selector(setAge:)]); // 0x10f760390 85 86 // p1 添加了观察者,setter方法发生改变 87 NSLog(@"%p",[p1 methodForSelector:@selector(setAge:)]); // 0x7fff258f10eb 88 // p (IMP)0x7fff258f10eb (Foundation`_NSSetIntValueAndNotify) 89 // 因为 Person属性 age是 int型,所以这里调用的是 _NSSetIntValueAndNotify函数 90 91 // 移除监听 92 [p1 removeObserver:self forKeyPath:@"age"]; 93 } 94 95 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ 96 97 NSLog(@"keyPath = %@ object = %@ change = %@",keyPath,object,change); 98 99 } 100 101 @end
日志信息:验证监听方法是在 didChangeValueForKey中触发的
② KVO流程分析图
③ 可以使用命令行 nm Foundation | grep ValueAndNotify 查询相对应的函数
如何手动启用 KVO
1 – 只需要添加监听的对象手动调用 willChangeValueForKey、didChangeValueForKey两方法,二者缺一不可
// – Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject{ @public NSString *_name; } @end
// – ViewController.m
1 // 其中,我们使用 lldb命令更好的展示 2 - (void)viewDidLoad { 3 [super viewDidLoad]; 4 self.view.backgroundColor = [UIColor cyanColor]; 5 6 Person *p1 = [Person new]; 7 p1 ->_name = @"123"; 8 [p1 addObserver:self forKeyPath:@"_name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"传参"]; 9 10 // 手动启用 KVO 11 [p1 willChangeValueForKey:@"_name"]; 12 p1 ->_name = @"456"; 13 [p1 didChangeValueForKey:@"_name"]; 14 15 [p1 removeObserver:self forKeyPath:@"_name"]; 16 } 17 18 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ 19 20 NSLog(@"keyPath = %@ object = %@ change = %@",keyPath,object,change); 21 22 }
日志信息:直接赋值,同样可以实现监听
结语
1 – KVO工作原理
① 当一个对象使用了 KVO监听,iOS系统会修改这个对象的 isa指针,重指向一个全新的通过 Runtime动态创建的子类
② 子类拥有自己的 set方法实现,内部会调用
willChangeValueForKey:
原来的 setter
didChangeValueForKey: 这个方法内部又会调用监听器的监听方法