参考官方文档(developer.apple.com/library/arc… ) html
来看下官方文档,KVO实现细节:ios
接下来咱们用代码深刻探讨下KVO到底作了些什么?上代码macos
#import "ViewController.h" @interface Person : NSObject @end @implementation Person //对类方法(类对象)进行动态方法决议(亦称方法动态解析) + (BOOL)resolveClassMethod:(SEL)sel{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel)); return [super resolveClassMethod:sel]; } //对实例方法(实例对象)进行动态方法决议(亦称方法动态解析) + (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel)); return [super resolveClassMethod:sel]; } //动态方法决议失败后,首先查找备用接收者,是否能直接处理消息(亦称快速转发) - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super forwardingTargetForSelector:aSelector]; } //动态方法决议失败后,查找备用接收者也失败(快速转发失败),那么就进行慢转发(须要进行方法签名) - (void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"%s - %@",__func__ ,anInvocation); return [super forwardInvocation:anInvocation]; } //对慢转发进行方法签名处理 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super methodSignatureForSelector:aSelector]; } - (IMP)methodForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super methodForSelector:aSelector]; } - (void)doesNotRecognizeSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super doesNotRecognizeSelector:aSelector]; } //是否容许自动触发KVO + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ NSLog(@"%s",__func__); return [super automaticallyNotifiesObserversForKey:key]; } //是否容许直接获取实例 + (BOOL)accessInstanceVariablesDirectly{ NSLog(@"%s",__func__); return [super accessInstanceVariablesDirectly]; } @end @interface ViewController () @property (nonatomic, strong) Person * person; @property (nonatomic ,assign) IMP imp,imp2,imp3,classImp,classImp2,classImp3; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; //添加属性观察,注意这里Person类并无添加任何属性 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"age"]; } @end 复制代码
运行结果以下:bash
2019-12-27 09:44:14.613488+0800 KVC&KVO[1353:39658] +[Person resolveClassMethod:] - keyPathsForValuesAffectingAge 2019-12-27 09:44:14.613624+0800 KVC&KVO[1353:39658] +[Person automaticallyNotifiesObserversForKey:] 2019-12-27 09:44:14.613719+0800 KVC&KVO[1353:39658] +[Person automaticallyNotifiesObserversOfAge] 2019-12-27 09:44:14.613844+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - setAge: 2019-12-27 09:44:14.613946+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - _setAge: 2019-12-27 09:44:14.614041+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - setIsAge: 2019-12-27 09:44:14.614126+0800 KVC&KVO[1353:39658] +[Person accessInstanceVariablesDirectly] 2019-12-27 09:44:14.614227+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - getAge 2019-12-27 09:44:14.614460+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - age 2019-12-27 09:44:14.614869+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - isAge 2019-12-27 09:44:14.615145+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - _getAge 2019-12-27 09:44:14.615406+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - _age 2019-12-27 09:44:14.615723+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - countOfAge 2019-12-27 09:44:14.615869+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - objectInAgeAtIndex: 2019-12-27 09:44:14.616045+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - ageAtIndexes: 2019-12-27 09:44:14.616200+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - indexInAgeOfObject: 2019-12-27 09:44:14.616381+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - enumeratorOfAge 2019-12-27 09:44:14.616540+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - memberOfAge: 2019-12-27 09:44:14.619180+0800 KVC&KVO[1353:39658] +[Person accessInstanceVariablesDirectly] 2019-12-27 09:44:14.619283+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex: 2019-12-27 09:44:14.619391+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertAge:atIndexes: 2019-12-27 09:44:14.619484+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex: 2019-12-27 09:44:14.619584+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAgeAtIndexes: 2019-12-27 09:44:14.619698+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex: 2019-12-27 09:44:14.619791+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertAge:atIndexes: 2019-12-27 09:44:14.619884+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex: 2019-12-27 09:44:14.619974+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAgeAtIndexes: 2019-12-27 09:44:14.620089+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - addAgeObject: 2019-12-27 09:44:14.620274+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAge: 2019-12-27 09:44:14.620441+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAgeObject: 2019-12-27 09:44:14.620610+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - addAge: 复制代码
结果分析:markdown
1.首先Person类并无添加任何age属性,咱们仍然注册观察Person对象属性age进行监听(这里不建议对一个不存在的属性进行观察,会带来异常),经过log你会看到KVC的影子,或许正好验证了 KVO是基于KVC的说法.架构
2.Person类并无age属性,添加KVO观察后,使用了动态方法解析,首先keyPathsForValuesAffectingAge(这里的Age就是咱们的age属性)这个方法解析经过(由于并无走消息转发流程,此方法主要来指明age是依赖哪些键,能够跟随依赖键更新而更新age自身)app
3.动态方法解析keyPathsForValuesAffectingAge成功后,而后调用automaticallyNotifiesObserversForKey:和 automaticallyNotifiesObserversOfAge是否容许Age这个key自动触发KVO(这里默认都是返回YES容许的),若是返回YES,而且option没有指定NSKeyValueObservingOptionInitial(由于这个并不会被拦截),则开始进行KVC对属性age进行处理,不然,直接返回,没有进行KVC的必要了(这里你能够重写automaticallyNotifiesObserversForKey:返回NO 或者 automaticallyNotifiesObserversOfAge返回NO,它确实中止了KVC流程)oop
4.若是容许对age属性自动KVO,那么接下来就对age属性开始执行KVC流程 (注意option指定NSKeyValueObservingOptionInitial,则会抛出NSUnknownKeyException崩溃,由于KVC首先走age属性的getter流程,发现没有getter一类方法,也没有实例变量,会动态解析生成getPrimitiveAge和primitiveAge方法,但同时并无生成age这个实例变量,因此Peson类没有age这个key致使崩溃)atom
5.KVC首先走age属性的setter流程(注意option指定NSKeyValueObservingOptionInitial则会先走getter流程),发现没有setter一类的存取方法,就去调用accessInstanceVariablesDirectly,查看是否容许直接获取实例变量(这里是容许),若是返回NO不容许,则会去动态解析setPrimitiveAge:,getPrimitiveAge 和 primitiveAge 方法,保证可以正确处理KVO. 注意此时KVC对age进行取值或者设值操做 都会抛出valueForUndefinedKey异常.同理接着顺序处理age属性的getter流程,以及age集合属性处理流程lua
主要分三步:
对简单非集合对象: -(void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
对于NSArray集合对象: -(void)addObserver:(NSObject *)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
重要:添加观察后,不须要监听的时候必须移除监听.通常在dealloc方法里移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
或者 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
在NSObject分类NSKeyValueObserverRegistration中有如下经常使用方法:
/* 添加键值观察 observer:观察者,也就是通知的订阅者(或者说监听者),观察者是必须的,并且不能为空 keyPath :被观察的属性 options :KVO配置相关,既会影响通知中提供的更改字典的内容,又会影响生成通知的方式,有四个选项,下面会介绍 context :上下文,主要区分通知来源 */ - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; /*移除监听 注意:必须保证在观察者被释放销毁以前移除,不然程序崩溃 */ - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0)); /* * 移除监听 注意:必须保证在观察者被释放销毁以前移除,不然程序崩溃 */ - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; 复制代码
NSKeyValueObservingOptions有四个选项
NSKeyValueObservingOptionNew 观察者回调监听中change字典中包含改变后的值
NSKeyValueObservingOptionOld 观察者回调监听中change字典中包含改变前的值
NSKeyValueObservingOptionInitial 注册后马上触发KVO通知,可是须要注意的是 NSKeyValueObservingOptions参数同时指定了NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial,首次触发KVO change字典中并不包含old值
NSKeyValueObservingOptionPrior 值改变前是否也通知(改变前通知一次,改变后再通知一次)
复制代码
来看点例子:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject { @public //暴露成员变量,仅仅为了演示给成员变量直接赋值,验证不能触发KVO NSUInteger _age; } -(void)setAge:(NSUInteger)age; @end NS_ASSUME_NONNULL_END #import "Person.h" @interface Person () @end @implementation Person -(void)setAge:(NSUInteger)age{ NSLog(@"%s",__func__); _age = age; } @end 复制代码
#import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic, strong) Person * person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; //添加属性观察 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; //注意这里直接给成员变量赋值,不会触发KVO _person->_age = 20; //这里调用自定义setter方法,验证是否会触发KVO [_person setAge:65]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"age"]; } @end 复制代码
运行结果分析: 咱们能够看到_person->_age = 20 直接赋值并无触发KVO(你能够将 [_person setAge:65];注释掉去验证,它确实没有触发KVO),而调用setter方法触发了KVO,由此能够猜想setter方法触发了KVO.可是有一点必定很疑惑,由于才刚说了直接赋值操做不能触发KVO,而咱们的setter方法仅仅作了一个赋值操做,但结果确很意外,它居然触发了KVO!由此能够下结论了,被观察的对象类中setter方法是无关紧要的,它不是必须的!!!接下来咱们将Person类中setter方法注释掉。因为如今没有明确的存取方法了,这个时候KVC能够来展现下本身的才能了!咱们将 [_person setAge:65] 换成KVC方法 [_person setValue:@25 forKey:@"age"];而且Persron添加以下方法
+ (BOOL)accessInstanceVariablesDirectly{ NSLog(@"%s",__func__); return [super accessInstanceVariablesDirectly]; } 复制代码
运行结果如咱们预期猜测的那样,可是也有疑惑,由于看到KVC打印结果,也是直接获取的对象实例,咱们也没有提供setter方法。
对象类中setter方法确实不是必须的.那么问题又来了,到底是什么触发的KVO? 官方文档中手动KVO有这么一段:
#import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic, strong) Person * person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; //添加属性观察 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; [_person willChangeValueForKey:@"age"]; _person->_age = 30; [_person didChangeValueForKey:@"age"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); } - (void)dealloc { NSLog(@"%s",__func__); //移除 [_person removeObserver:self forKeyPath:@"age"]; } @end 复制代码
运行结果也如预期的同样触发KVO( willChangeValueForKey:和 didChangeValueForKey:确实触发KVO通知了,中间直接赋值操做无关紧要)
接下来咱们来验证第1种猜测 代码以下:
其中Person.m增长方法resolveInstanceMethod: 便于验证
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel)); return [super resolveClassMethod:sel]; } 复制代码
#import "ViewController.h" #import "Person.h" #import <objc/message.h> @interface ViewController () @property (nonatomic, strong) Person * person; @property (nonatomic ,assign) IMP imp,imp2,imp3,classImp,classImp2,classImp3; @end @implementation ViewController //获取对象全部属性 NSArray<NSString*>*getAllProperties(Class cls){ unsigned int count; objc_property_t * properties = class_copyPropertyList(cls, &count); NSMutableArray* arr = [NSMutableArray array]; for (int i = 0; i < count; i++) { objc_property_t property= properties[i]; const char * p_name = property_getName(property); NSString* name = [NSString stringWithCString:p_name encoding:NSUTF8StringEncoding]; [arr addObject:name]; } free(properties); return arr.copy; } //获取对象全部实例变量 NSArray<NSString*>*getAllIvars(Class cls){ unsigned int count; Ivar * ivars = class_copyIvarList(cls, &count); NSMutableArray* arr = [NSMutableArray array]; for (int i = 0; i < count; i++) { Ivar ivar= ivars[i]; const char * ivar_name = ivar_getName(ivar); NSString* name = [NSString stringWithCString:ivar_name encoding:NSUTF8StringEncoding]; [arr addObject:name]; } free(ivars); return arr.copy; } //获取对象全部方法 NSArray<NSString*>*getAllMethods(Class cls){ unsigned int count; Method * methods = class_copyMethodList(cls, &count); NSMutableArray* arr = [NSMutableArray array]; for (int i = 0; i < count; i++) { Method method = methods[i]; SEL sel = method_getName(method); NSString* name = NSStringFromSelector(sel); [arr addObject:name]; } free(methods); return arr.copy; } - (void)viewDidLoad { [super viewDidLoad]; _person = [[Person alloc] init]; Class classP = object_getClass(_person); //观察前 NSLog(@"观察前对象 :%@",_person); NSLog(@"观察前对象类 :%@",classP); NSLog(@"观察前对象父类 :%@",[classP superclass]); NSLog(@"观察前全部属性 :%@",getAllProperties(classP)); NSLog(@"观察前全部变量 :%@",getAllIvars(classP)); NSLog(@"观察前全部方法 :%@",getAllMethods(classP)); self.imp = [_person methodForSelector:@selector(setAge:)]; self.classImp = [_person methodForSelector:@selector(class)]; //添加属性观察 [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; //触发KVO [_person setAge:200]; //观察后 Class classP2 = object_getClass(_person);// 与 _person->isa等价 注意这里不能_person->isa调用,由于默认isa是@protected NSLog(@"观察后对象 :%@",_person); NSLog(@"观察后对象类:%@",classP2); NSLog(@"观察后对象父类 :%@",[classP2 superclass]); NSLog(@"观察后全部属性 :%@",getAllProperties(classP2)); NSLog(@"观察后全部变量 :%@",getAllIvars(classP2)); NSLog(@"观察后全部方法 :%@",getAllMethods(classP2)); self.imp2 = [_person methodForSelector:@selector(setAge:)]; self.classImp2 = [_person methodForSelector:@selector(class)]; //移除观察,通常放在dealloc方法移除,这里仅仅为了演示移除观察后的效果 [_person removeObserver:self forKeyPath:@"age"]; Class classP3 = object_getClass(_person); NSLog(@"移除观察后对象 :%@",_person); NSLog(@"移除观察后对象类:%@",classP3); NSLog(@"移除观察后对象父类 :%@",[classP3 superclass]); NSLog(@"移除观察后全部属性 :%@",getAllProperties(classP3)); NSLog(@"移除观察后全部变量 :%@",getAllIvars(classP3)); NSLog(@"移除观察后全部方法 :%@",getAllMethods(classP3)); self.imp3 = [_person methodForSelector:@selector(setAge:)]; self.classImp3 = [_person methodForSelector:@selector(class)]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%s",__func__); NSLog(@"keyPath = %@",keyPath); NSLog(@"change = %@",change); } //- (void)dealloc //{ // NSLog(@"%s",__func__); // //移除 // [_person removeObserver:self forKeyPath:@"age"]; //} @end 复制代码
打印有点多,具体看log吧
2019-12-26 14:20:01.558958+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - get_isKVOA 2019-12-26 14:20:01.559114+0800 KVC&KVO[3067:148054] 观察前_isKVOA返回值:0 2019-12-26 14:20:01.559212+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 14:20:01.559305+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - descriptionWithLocale: 2019-12-26 14:20:01.559403+0800 KVC&KVO[3067:148054] 观察前对象 :<Person: 0x6000008745e0> 2019-12-26 14:20:01.559489+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 14:20:01.559576+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - descriptionWithLocale: 2019-12-26 14:20:01.559685+0800 KVC&KVO[3067:148054] 观察前对象类 :Person 2019-12-26 14:20:01.559933+0800 KVC&KVO[3067:148054] 观察前对象父类 :NSObject 2019-12-26 14:20:01.560332+0800 KVC&KVO[3067:148054] 观察前全部属性 :( ) 2019-12-26 14:20:01.560978+0800 KVC&KVO[3067:148054] 观察前全部变量 :( "_age" ) 2019-12-26 14:20:01.561398+0800 KVC&KVO[3067:148054] 观察前全部方法 :( "setAge:" ) 2019-12-26 14:20:01.561817+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - keyPathsForValuesAffectingAge 2019-12-26 14:20:01.562166+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - automaticallyNotifiesObserversOfAge 2019-12-26 14:20:01.562608+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - getAge 2019-12-26 14:20:01.562920+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - age 2019-12-26 14:20:01.563081+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - isAge 2019-12-26 14:20:01.563286+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _getAge 2019-12-26 14:20:01.563470+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _age 2019-12-26 14:20:01.563638+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - countOfAge 2019-12-26 14:20:01.567939+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - objectInAgeAtIndex: 2019-12-26 14:20:01.568043+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - ageAtIndexes: 2019-12-26 14:20:01.568134+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - indexInAgeOfObject: 2019-12-26 14:20:01.568232+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - enumeratorOfAge 2019-12-26 14:20:01.568324+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - memberOfAge: 2019-12-26 14:20:01.568427+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex: 2019-12-26 14:20:01.568520+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertAge:atIndexes: 2019-12-26 14:20:01.568606+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex: 2019-12-26 14:20:01.568782+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAgeAtIndexes: 2019-12-26 14:20:01.569057+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex: 2019-12-26 14:20:01.569271+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertAge:atIndexes: 2019-12-26 14:20:01.569484+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex: 2019-12-26 14:20:01.569751+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAgeAtIndexes: 2019-12-26 14:20:01.570040+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - addAgeObject: 2019-12-26 14:20:01.570286+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAge: 2019-12-26 14:20:01.570553+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAgeObject: 2019-12-26 14:20:01.570863+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - addAge: 2019-12-26 14:20:01.571093+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - getAge 2019-12-26 14:20:01.571345+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - age 2019-12-26 14:20:01.571596+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - isAge 2019-12-26 14:20:01.571841+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _getAge 2019-12-26 14:20:01.572090+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _age 2019-12-26 14:20:01.572272+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - countOfAge 2019-12-26 14:20:01.572501+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - objectInAgeAtIndex: 2019-12-26 14:20:01.572756+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - ageAtIndexes: 2019-12-26 14:20:01.573008+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - indexInAgeOfObject: 2019-12-26 14:20:01.573191+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - enumeratorOfAge 2019-12-26 14:20:01.573421+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - memberOfAge: 2019-12-26 14:20:01.573698+0800 KVC&KVO[3067:148054] -[Person setAge:] 2019-12-26 14:20:01.573972+0800 KVC&KVO[3067:148054] -[ViewController observeValueForKeyPath:ofObject:change:context:] 2019-12-26 14:20:01.574154+0800 KVC&KVO[3067:148054] keyPath = age 2019-12-26 14:20:01.574458+0800 KVC&KVO[3067:148054] change = { kind = 1; new = 300; old = 0; } 2019-12-26 14:20:01.574798+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - get_isKVOA 2019-12-26 14:20:01.575064+0800 KVC&KVO[3067:148054] 观察后_isKVOA返回值:1 2019-12-26 14:20:01.575333+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 14:20:01.575560+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - descriptionWithLocale: 2019-12-26 14:20:01.575835+0800 KVC&KVO[3067:148054] 观察后对象 :<Person: 0x6000008745e0> 2019-12-26 14:20:01.576035+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 14:20:01.576254+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - descriptionWithLocale: 2019-12-26 14:20:01.576486+0800 KVC&KVO[3067:148054] 观察后对象类:NSKVONotifying_Person 2019-12-26 14:20:01.576697+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 14:20:01.576935+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - descriptionWithLocale: 2019-12-26 14:20:01.577174+0800 KVC&KVO[3067:148054] 观察后对象父类 :Person 2019-12-26 14:20:01.577406+0800 KVC&KVO[3067:148054] 观察后全部属性 :( ) 2019-12-26 14:20:01.577623+0800 KVC&KVO[3067:148054] 观察后全部变量 :( ) 2019-12-26 14:20:01.577849+0800 KVC&KVO[3067:148054] 观察后全部方法 :( "setAge:", class, dealloc, "_isKVOA" ) 2019-12-26 14:20:01.578021+0800 KVC&KVO[3067:148054] 移除观察后_isKVOA返回值:0 2019-12-26 14:20:05.452737+0800 KVC&KVO[3067:148054] 移除观察后对象 :<Person: 0x6000008745e0> 2019-12-26 14:20:05.452894+0800 KVC&KVO[3067:148054] 移除观察后对象类:Person 2019-12-26 14:20:05.452995+0800 KVC&KVO[3067:148054] 移除观察后对象父类 :NSObject 2019-12-26 14:20:05.453081+0800 KVC&KVO[3067:148054] 移除观察后全部属性 :( ) 2019-12-26 14:20:05.453169+0800 KVC&KVO[3067:148054] 移除观察后全部变量 :( "_age" ) 2019-12-26 14:20:05.453263+0800 KVC&KVO[3067:148054] 移除观察后全部方法 :( "setAge:" ) 复制代码
运行结果分析:
到这里理论上说完了,可是还存在一种状况,就是父类Person没有提供setAge:方法,只提供一个成员变量_age,可是使用KVC一样能够触发KVO,这又是为何呢?下面经过结合KVC来验证,这里使用 [_person setValue:@300 forKey:@"age"];来触发KVO
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject { @public //暴露成员变量,仅仅为了演示给成员变量直接赋值,验证不能触发KVO NSUInteger _age; } @end NS_ASSUME_NONNULL_END #import "Person.h" @interface Person () @end @implementation Person + (BOOL)accessInstanceVariablesDirectly{ NSLog(@"%s",__func__); return [super accessInstanceVariablesDirectly]; } @end 复制代码
运行结果分析: 这里Person并无提供setAge:方法,使用KVC设值,也是直接获取的对象实例,注册KVO后,一样生成了一个派生类NSKVONotifying_Person,这个派生类没有添加setter方法,只重写了dealloc方法和class方法,增长了一个_isKVOA方法,不一样的是imp和imp2变成了_objc_msgForward,这样相似于上面的猜测2,使用了runtime动态方法解析或转发,接下来咱们就向Person类种重写消息转发的几个经常使用方法:
#import "Person.h" @interface Person () @end @implementation Person + (BOOL)resolveClassMethod:(SEL)sel{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel)); return [super resolveClassMethod:sel]; } + (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel)); return [super resolveClassMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super forwardingTargetForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"%s - %@",__func__ ,anInvocation); return [super forwardInvocation:anInvocation]; } - (IMP)methodForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super methodForSelector:aSelector]; } - (void)doesNotRecognizeSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector)); return [super doesNotRecognizeSelector:aSelector]; } + (BOOL)accessInstanceVariablesDirectly{ NSLog(@"%s",__func__); return [super accessInstanceVariablesDirectly]; } @end 复制代码
运行结果以下(能够顺便看下KVC执行流程):
2019-12-26 10:52:51.962332+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 10:52:51.962469+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - descriptionWithLocale: 2019-12-26 10:52:51.962572+0800 KVC&KVO[1368:47095] 观察前对象 :<Person: 0x600003894530> 2019-12-26 10:52:51.962659+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 10:52:51.962744+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - descriptionWithLocale: 2019-12-26 10:52:51.962824+0800 KVC&KVO[1368:47095] 观察前对象类 :Person 2019-12-26 10:52:51.962902+0800 KVC&KVO[1368:47095] 观察前对象父类 :NSObject 2019-12-26 10:52:51.963032+0800 KVC&KVO[1368:47095] 观察前全部属性 :( ) 2019-12-26 10:52:51.963425+0800 KVC&KVO[1368:47095] 观察前全部变量 :( "_age" ) 2019-12-26 10:52:51.963819+0800 KVC&KVO[1368:47095] 观察前全部方法 :( "doesNotRecognizeSelector:", "forwardingTargetForSelector:", "methodForSelector:", "forwardInvocation:" ) 2019-12-26 10:52:51.964023+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - setAge: 2019-12-26 10:52:51.964311+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge: 2019-12-26 10:52:51.964684+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - class 2019-12-26 10:52:51.965136+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - keyPathsForValuesAffectingAge 2019-12-26 10:52:51.965465+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - automaticallyNotifiesObserversOfAge 2019-12-26 10:52:51.965831+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge: 2019-12-26 10:52:51.966028+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _setAge: 2019-12-26 10:52:51.966324+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setIsAge: 2019-12-26 10:52:51.973082+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly] 2019-12-26 10:52:51.973216+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - getAge 2019-12-26 10:52:51.973330+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - age 2019-12-26 10:52:51.973428+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - isAge 2019-12-26 10:52:51.973524+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _getAge 2019-12-26 10:52:51.973612+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _age 2019-12-26 10:52:51.973702+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - countOfAge 2019-12-26 10:52:51.973792+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - objectInAgeAtIndex: 2019-12-26 10:52:51.973891+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - ageAtIndexes: 2019-12-26 10:52:51.973989+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - indexInAgeOfObject: 2019-12-26 10:52:51.974202+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - enumeratorOfAge 2019-12-26 10:52:51.974378+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - memberOfAge: 2019-12-26 10:52:51.974552+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly] 2019-12-26 10:52:51.974779+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex: 2019-12-26 10:52:51.975018+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertAge:atIndexes: 2019-12-26 10:52:51.975243+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex: 2019-12-26 10:52:51.975466+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAgeAtIndexes: 2019-12-26 10:52:51.975685+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex: 2019-12-26 10:52:51.975868+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertAge:atIndexes: 2019-12-26 10:52:51.976051+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex: 2019-12-26 10:52:51.976241+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAgeAtIndexes: 2019-12-26 10:52:51.976468+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - addAgeObject: 2019-12-26 10:52:51.976682+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAge: 2019-12-26 10:52:51.976888+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAgeObject: 2019-12-26 10:52:51.977102+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - addAge: 2019-12-26 10:52:51.977341+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge: 2019-12-26 10:52:51.977544+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _setAge: 2019-12-26 10:52:51.977741+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setIsAge: 2019-12-26 10:52:51.977934+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly] 2019-12-26 10:52:51.978175+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - getAge 2019-12-26 10:52:51.978365+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - age 2019-12-26 10:52:51.978580+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - isAge 2019-12-26 10:52:51.978813+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _getAge 2019-12-26 10:52:51.979030+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _age 2019-12-26 10:52:51.979249+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - countOfAge 2019-12-26 10:52:51.979461+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - objectInAgeAtIndex: 2019-12-26 10:52:51.979712+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - ageAtIndexes: 2019-12-26 10:52:51.979951+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - indexInAgeOfObject: 2019-12-26 10:52:51.980183+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - enumeratorOfAge 2019-12-26 10:52:51.980433+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - memberOfAge: 2019-12-26 10:52:51.980666+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly] 2019-12-26 10:52:51.980926+0800 KVC&KVO[1368:47095] -[ViewController observeValueForKeyPath:ofObject:change:context:] 2019-12-26 10:52:51.981215+0800 KVC&KVO[1368:47095] keyPath = age 2019-12-26 10:52:51.981500+0800 KVC&KVO[1368:47095] change = { kind = 1; new = 300; old = 0; } 2019-12-26 10:52:51.981763+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 10:52:51.982011+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - descriptionWithLocale: 2019-12-26 10:52:51.982258+0800 KVC&KVO[1368:47095] 观察后对象 :<Person: 0x600003894530> 2019-12-26 10:52:51.982467+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 10:52:51.982684+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - descriptionWithLocale: 2019-12-26 10:52:51.982925+0800 KVC&KVO[1368:47095] 观察后对象类:NSKVONotifying_Person 2019-12-26 10:52:51.983145+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString: 2019-12-26 10:52:51.983355+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - descriptionWithLocale: 2019-12-26 10:52:51.983568+0800 KVC&KVO[1368:47095] 观察后对象父类 :Person 2019-12-26 10:52:51.983824+0800 KVC&KVO[1368:47095] 观察后全部属性 :( ) 2019-12-26 10:52:51.984010+0800 KVC&KVO[1368:47095] 观察后全部变量 :( ) 2019-12-26 10:52:51.984210+0800 KVC&KVO[1368:47095] 观察后全部方法 :( class, dealloc, "_isKVOA" ) 2019-12-26 10:52:51.984429+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - setAge: 2019-12-26 10:52:51.984654+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge: 2019-12-26 10:52:51.984875+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - class 2019-12-26 10:52:51.985099+0800 KVC&KVO[1368:47095] 移除观察后对象 :<Person: 0x600003894530> 2019-12-26 10:52:51.985277+0800 KVC&KVO[1368:47095] 移除观察后对象类:Person 2019-12-26 10:52:51.985466+0800 KVC&KVO[1368:47095] 移除观察后对象父类 :NSObject 2019-12-26 10:52:51.985674+0800 KVC&KVO[1368:47095] 移除观察后全部属性 :( ) 2019-12-26 10:52:51.985875+0800 KVC&KVO[1368:47095] 移除观察后全部变量 :( "_age" ) 2019-12-26 10:52:51.986070+0800 KVC&KVO[1368:47095] 移除观察后全部方法 :( "doesNotRecognizeSelector:", "forwardingTargetForSelector:", "methodForSelector:", "forwardInvocation:" ) 2019-12-26 10:52:51.986264+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - setAge: 2019-12-26 10:52:51.986515+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge: 2019-12-26 10:52:51.986763+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - class 复制代码
结果分析: KVO并不必定会重写setter方法,前提是父类中存在setter方法则会重写,不存在,则会动态解析setter方法,再获取实例并在改变实例的先后注入willChangeValueForKey:和didChangeValueForKey:方法来触发KVO
当一个NSObejct对象或者其子类对象(暂且记为XXX)某个属性(属性暂且记为m)被观察时,会派生一个子类NSKVONotifying_XXX,此时父类XXX的isa指针会指向其派生类NSKVONotifying_XXX,该子类会先检查父类XXX是否存在setM:方法,若是存在,则会重写setM:方法,并注入willChangeValueForKey:和didChangeValueForKey:方法来触发KVO .若是不存在setM:方法,则不会添加该方法,而是动态解析setM:方法,再获取父类实例变量,而且在赋值实例变量的先后注入willChangeValueForKey:和didChangeValueForKey:方法来触发KVO. 子类NSKVONotifying_XXX重写的方法还有class,和dealloc方法。子类NSKVONotifying_XXX同时增长了一个_isKVOA方法. 当移除观察时,被观察的对象isa又从新指向自身类XXX
若有不正确的的地方(主要是:是否重写setter方法),欢迎进行交流!