探討KVO(OC)底層實現原理(一)

參考官方文檔(developer.apple.com/library/arc…html

image.png

KVO(Key-Value Observing)鍵值觀察,是觀察者模式的一種衍生。基本思想就是:經過KVO接口方法,給對象屬性添加觀察,並指定監聽觀察者,被觀察對象的屬性發生變化時,能夠觸發觀察者實現的KVO接口方法,來自動通知監聽觀察者

  • KVO也是NSObject的一種非正式協議,凡是繼承了NSObject的對象均可以使用KVO. KVO提供了一種機制,該機制容許將其餘對象的特定屬性的更改通知對象。iOS 應用程序MVC架構模式中模型層和控制器層之間的通訊會常常用到KVO。在OS X中,控制器層綁定技術(Cocoa Bingdings)在很大程度上依賴於鍵值觀察.
  • 控制器對象一般觀察模型對象的屬性,而視圖對象經過控制器觀察模型對象的屬性。
  • 模型對象能夠觀察其餘模型對象(一般用於肯定從屬值什麼時候更改),甚至能夠觀察自身(再次肯定從屬值什麼時候更改)

來看下官方文檔,KVO實現細節:ios

KVO實現細節

image.png
自動KVO使用一種稱爲isa-swizzling的技術實現的.isa指針指向維護分發表的對象類。 該分發表實質上包含指向該類實現的方法的指針以及其餘數據.在爲對象的屬性註冊觀察者時,將修改觀察對象的isa指針,指向中間類(具體使用了runtime動態機制生成了其子類,並重寫dealloc和class方法,增長_isKVOA方法)而不是真實類(該對象所屬類)。 這樣,isa指針的值不必定反映實例的實際類.所以Apple也建議永遠不該依靠isa指針來肯定類的類型。 相反,應該使用class方法來肯定對象實例的類

接下來咱們用代碼深刻探討下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

KVO的使用

主要分三步:

1.添加屬性監聽

對簡單非集合對象: -(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;

2.實現觀察者監聽回調接口:

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

3.移除監聽

重要:添加觀察後,不須要監聽的時候必須移除監聽.通常在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  值改變前是否通知(改變前通知一次,改變後再通知一次)
複製代碼

簡單屬性使用KVO

來看點例子:

#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
複製代碼

image.png

運行結果分析: 咱們能夠看到_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方法。

image.png

對象類中setter方法確實不是必須的.那麼問題又來了,到底是什麼觸發的KVO? 官方文檔中手動KVO有這麼一段:

image.png
文檔中,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通知了,中間直接賦值操做無關緊要)

image.png
到這裏咱們能夠知道觸發KVO的兩個核心方法了,可是仍是有一個疑惑,就是怎麼實現的呢?這裏有兩種猜測:

  • 1.對象添加註冊觀察時,採用了派生類機制,父類isa指針指向派生類,並重寫父類setter方法,並加入了willChangeValueForKey:和didChangeValueForKey:方法
  • 2.對象添加註冊觀察時,使用了runtime動態機制,在改變實例變量的先後(也即賦值操做先後)分別注入了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
複製代碼

image.png

打印有點多,具體看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:"
)
複製代碼

運行結果分析:

  • 1.觀察先後以及移除觀察後,被觀察對象_person地址沒有變化
  • 2.觀察後,動態生成了Person類的一個子類 NSKVONotifying_Person,而且_person實例對象isa指向NSKVONotifying_Person
  • 3.子類NSKVONotifying_Person重寫setAge:方法,由KVC&KVO(這裏KVC&KVO是我項目名稱)-[Person setAge:] 變成了Foundation_NSSetUnsignedLongLongValueAndNotify方法,而且重寫的setter方法中加入了 //值改變以前 [self willChangeValueForKey:@"age"]; [super setAge:age]; //由於它調用了父類的setAge:方法 //值改變以後 [self didChangeValueForKey:@"age"]; 這樣就觸發了KVO
  • 4.子類NSKVONotifying_Person重寫了class方法,由[NSObject class]變成了Foundation`NSKVOClass
  • 5.子類NSKVONotifying_Person重寫了dealloc方法(NSObject基類中存在dealloc方法),作一些清理工做
  • 6.Person類及其子類NSKVONotifying_Person都動態增長了_isKVOA方法(此方法NSObejct基類中並無,能夠看到在觀察前和觀察後,動態方法解析了,增長了_isKVOA方法),猜想用來標記對象是否被添加KVO觀察了
  • 7.移除觀察後,_person實例對象isa指向Person類
  • 8.觀察前和移除觀察後 _isKOVA均返回false ,而添加觀察後_isKOVA返回true,由此基本能夠肯定_isKOVA用來標記當前對象是否被添加觀察

到這裏理論上說完了,可是還存在一種狀況,就是父類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
複製代碼

image.png

運行結果分析: 這裏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執行流程):

image.png

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

最後總結-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方法),歡迎進行交流!

相關文章
相關標籤/搜索