NSKVONotifyin_Person内部结构是怎样的?
首先我们知道,NSKVONotifyin_Person作为Person的子类,其superclass指针指向Person类,并且NSKVONotifyin_Person内部一定对setAge方法做了单独的实现,那么NSKVONotifyin_Person同Person类的差别可能就在于其内存储的对象方法及实现不同。 我们通过runtime分别打印Person类对象和NSKVONotifyin_Person类对象内存储的对象方法
{ p1.age = 10; [self printMethods: object_getClass(p2)]; [self printMethods: object_getClass(p1)]; [p1 removeObserver:self forKeyPath:@"age"]; } // runtime to print class methods - (void)printMethods:(Class)cls { unsigned int count ; Method *methods = class_copyMethodList(cls, &count); NSMutableString *methodNames = [NSMutableString string]; [methodNames appendFormat:@"%@ method list: ", cls]; for (int i = 0 ; i < count; i++) { Method method = methods[i]; NSString *methodName = NSStringFromSelector(method_getName(method)); [methodNames appendString:@"\n"]; [methodNames appendString: methodName]; } NSLog(@"%@",methodNames); free(methods); }
上述打印内容如下
通过上述代码我们发现NSKVONotifyin_Person中有4个对象方法。分别为setAge: class dealloc _isKVOA,那么至此我们可以画出NSKVONotifyin_Person的内存结构以及方法调用顺序。
这里NSKVONotifyin_Person重写class方法是为了隐藏NSKVONotifyin_Person。不被外界所看到。我们在p1添加过KVO监听之后,分别打印p1和p2对象的class可以发现他们都返回Person。
NSLog(@"%@,%@",[p1 class],[p2 class]);
如果NSKVONotifyin_Person不重写class方法,那么当对象要调用class对象方法的时候就会一直向上找来到nsobject,而nsobect的class的实现大致为返回自己isa指向的类,返回p1的isa指向的类那么打印出来的类就是NSKVONotifyin_Person,但是apple不希望将NSKVONotifyin_Person类暴露出来,并且不希望我们知道NSKVONotifyin_Person内部实现,所以在内部重写了class类,直接返回Person类,所以外界在调用p1的class对象方法时,是Person类。这样p1给外界的感觉p1还是Person类,并不知道NSKVONotifyin_Person子类的存在。 那么我们可以猜测NSKVONotifyin_Person内重写的class内部实现大致为
- (Class) class { // 得到类对象,在找到类对象父类 return class_getSuperclass(object_getClass(self)); }
验证
验证 didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法 我们在Person类中重写willChangeValueForKey:和didChangeValueForKey:方法,模拟他们的实现。
- (void)setAge:(int)age { NSLog(@"setAge:"); _age = age; } - (void)willChangeValueForKey:(NSString *)key { NSLog(@"willChangeValueForKey: - begin"); [super willChangeValueForKey:key]; NSLog(@"willChangeValueForKey: - end"); } - (void)didChangeValueForKey:(NSString *)key { NSLog(@"didChangeValueForKey: - begin"); [super didChangeValueForKey:key]; NSLog(@"didChangeValueForKey: - end"); }
再次运行来查看didChangeValueForKey的方法内运行过程,通过打印内容可以看到,确实在didChangeValueForKey方法内部已经调用了observer的observeValueForKeyPath:ofObject:change:context:方法。
如何手动调用 KVO
被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKey和didChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。
{ Person *p1 = [[Person alloc] init]; p1.age = 1.0; NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; [p1 willChangeValueForKey:@"age"]; [p1 didChangeValueForKey:@"age"]; [p1 removeObserver:self forKeyPath:@"age"]; }
通过打印我们可以发现,didChangeValueForKey方法内部成功调用了observeValueForKeyPath:ofObject:change:context:,并且age的值并没有发生改变。
小结
当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。