參考官方文檔(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:
複製代碼
結果分析:架構
1.首先Person類並無添加任何age屬性,咱們仍然註冊觀察Person對象屬性age進行監聽(這裏不建議對一個不存在的屬性進行觀察,會帶來異常),經過log你會看到KVC的影子,或許正好驗證了 KVO是基於KVC的說法.app
2.Person類並無age屬性,添加KVO觀察後,使用了動態方法解析,首先keyPathsForValuesAffectingAge(這裏的Age就是咱們的age屬性)這個方法解析經過(由於並無走消息轉發流程,此方法主要來指明age是依賴哪些鍵,能夠跟隨依賴鍵更新而更新age自身)ui
3.動態方法解析keyPathsForValuesAffectingAge成功後,而後調用automaticallyNotifiesObserversForKey:和 automaticallyNotifiesObserversOfAge是否容許Age這個key自動觸發KVO(這裏默認都是返回YES容許的),若是返回YES,而且option沒有指定NSKeyValueObservingOptionInitial(由於這個並不會被攔截),則開始進行KVC對屬性age進行處理,不然,直接返回,沒有進行KVC的必要了(這裏你能夠重寫automaticallyNotifiesObserversForKey:返回NO 或者 automaticallyNotifiesObserversOfAge返回NO,它確實中止了KVC流程)atom
4.若是容許對age屬性自動KVO,那麼接下來就對age屬性開始執行KVC流程 (注意option指定NSKeyValueObservingOptionInitial,則會拋出NSUnknownKeyException崩潰,由於KVC首先走age屬性的getter流程,發現沒有getter一類方法,也沒有實例變量,會動態解析生成getPrimitiveAge和primitiveAge方法,但同時並無生成age這個實例變量,因此Peson類沒有age這個key致使崩潰)lua
5.KVC首先走age屬性的setter流程(注意option指定NSKeyValueObservingOptionInitial則會先走getter流程),發現沒有setter一類的存取方法,就去調用accessInstanceVariablesDirectly,查看是否容許直接獲取實例變量(這裏是容許),若是返回NO不容許,則會去動態解析setPrimitiveAge:,getPrimitiveAge 和 primitiveAge 方法,保證可以正確處理KVO. 注意此時KVC對age進行取值或者設值操做 都會拋出valueForUndefinedKey異常.同理接着順序處理age屬性的getter流程,以及age集合屬性處理流程spa
主要分三步:
對簡單非集合對象: -(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有這麼一段:
文檔中,Apple 只是經過例子告訴咱們,設值以前使用willChangeValueForKey:和設值以後使用 didChangeValueForKey:就能夠發送通知了.那麼咱們依葫蘆畫瓢:#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通知了,中間直接賦值操做無關緊要)
到這裏咱們能夠知道觸發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)某個屬性或者某個成員變量(上面已經驗證能夠對成員變量進行KVO,不必定要求是屬性,這裏暫且成員變量記爲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方法),歡迎進行交流!