iOS底層原理探索:KVO 1.準備工作 2.開始觀察

使用稱爲isa swizzling的技術實現自動鍵值觀察。顧名思義,isa指針指向維護分派表的對象類。這個分派表本質上包含指向類實現的方法的指針以及其他數據。
當一個觀察者爲一個對象的屬性註冊時,被觀察對象的isa指針被修改,指向一箇中間類而不是真正的類。因此,isa指針的值不一定反映實例的實際類。
決不能依賴isa指針來確定類成員身份。相反,您應該使用class方法來確定對象實例的類。

1.準備工作

先定義一個類NXBookclass,再定義好一個工具方法打印前後類的變化,

@interface NXBookclass : NSObject {
    @public NSInteger _priceValue;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSMutableArray *descriptions;
@end

@implementation NXBookclass
//+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
//    return YES;
//}
@end
@interface NXApi : NSObject
+ (NSMutableDictionary *)descriptionClass:(Class)cls;
@end

@implementation NXApi
+ (NSMutableDictionary *)descriptionClass:(Class)cls {
    NSMutableDictionary *dicValue = [NSMutableDictionary dictionaryWithCapacity:5];
    while (cls && cls != [NSObject class]) {
        NSMutableDictionary *dicSubvalue = [NSMutableDictionary dictionaryWithCapacity:5];
        dicSubvalue[@"varList"] = [NXApi varList:cls forward:false];
        dicSubvalue[@"methodList"] = [NXApi methodList:cls forward:false];
        dicSubvalue[@"metaClass"] = [NXApi metaClass:cls forward:false];
        [dicValue setObject:dicSubvalue forKey:[NSString stringWithCString:class_getName(cls) encoding:NSUTF8StringEncoding]];
        cls = class_getSuperclass(cls);
    }
    return dicValue;
}
@end

2.開始觀察

@interface NXKVO : NSObject
- (void)KVO;
@end

@implementation NXKVO
- (void)KVO{
    NXBookclass *bookclass = [[NXBookclass alloc] init];
    NSLog(@"觀察前:%@", [NXApi descriptionClass:object_getClass(bookclass)]);//打印觀察之前的類信息

  {
        [bookclass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL];

        NSLog(@"觀察中:%@", [NXApi descriptionClass:object_getClass(bookclass)]);//打印觀察之後的類信息

        bookclass.name = @"Swift";
        [bookclass removeObserver:self forKeyPath:@"name"];
  }

  NSLog(@"取消觀察後:%@", [NXApi descriptionClass:object_getClass(bookclass)]);//打印取消觀察之後的類信息
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%s, key=%@, change=%@", __func__, keyPath, change);
}
@end

運行如上代碼,打印信息如下:

觀察前或取消觀察後:
{
    NXBookclass =     {
        metaClass =         (
            NXBookclass
        );
        methodList =         (
            name,
            ".cxx_destruct",
            "setName:",
            descriptions,
            "setDescriptions:"
        );
        propertyList =         (
            name,
            descriptions
        );
        varList =         (
            "_priceValue",
            "_name",
            "_descriptions"
        );
    };
}

觀察中:
{
    "NSKVONotifying_NXBookclass" =     {
        metaClass =         (
            "NSKVONotifying_NXBookclass"
        );
        methodList =         (
            "setName:",
            class,
            dealloc,
            "_isKVOA"
        );
        propertyList =         (
        );
        varList =         (
        );
    };
    NXBookclass =     {
        metaClass =         (
            NXBookclass
        );
        methodList =         (
            name,
            ".cxx_destruct",
            "setName:",
            descriptions,
            "setDescriptions:"
        );
        propertyList =         (
            name,
            descriptions
        );
        varList =         (
            "_priceValue",
            "_name",
            "_descriptions"
        );
    };
}

觀察的回調信息:
key=name, change={
    kind = 1;
    new = Swift;
    old = "<null>";
}

通過打印的日誌信息,我們可以看到,我們的bookclass的的name屬性的觀察是成功的,name的值由null變成了Swift。

通過打印的日誌我們可以看到如下現象:觀察中的類與觀察前後的類信息不一樣,默認的類是NXBookclass->NSObject;而觀察中的類是NSKVONotifying_NXBookclass->NXBookclass->NSObject。並且在NSKVONotifying_NXBookclass這個類中出現了setName:,class,dealloc,_isKVOA4個新增的方法。

1.iOS對KVO底層的實現,其實是通過派生一個NSKVONotifying_XXXX的類。bookclass.is指向NSKVONotifying_NXBookclass, 我們通過訪問[bookclass class]會發現返回的仍然是NXBookclass, 這是因爲NSKVONotifying_NXBookclass重寫了class類,返回了NXBookclass

2.觀察實際上是通過重寫觀察的屬性的setter方法來實現的。重寫setName:方法:

- (void)setName:(NSString *)name {
    if(_name != name){
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

3._isKVOA,用來標記是否是一個KVO的實例,通過調試發現_isKVOA == YES。在最後一個removeObserver:forKeyPath:之後變成NO。

4.dealloc方法中,會將isa指回去。

5.通過觀察前,觀察後,取消觀察後對NSClassFromString(@"NSKVONotifying_NXBookclass")訪問,可以發現首次觀察之前結果爲null,觀察中和取消觀察後都存在,也就是取消觀察後NSKVONotifying_NXBookclass不會消失。如果存在自定義的類NSKVONotifying_NXBookclass,那麼在進行註冊觀察的時候會失敗,也就是觀察並不會生效。

6.默認情況下類的automaticallyNotifiesObserversForKey :返回YES,runtime會自動派生子類幫助我們做好監聽。我們可以重寫該方法 返回NO,並且自己在當前類中重寫setName:方法,這樣系統不會派生新的類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章