參考官方文檔(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方法),歡迎進行交流!