KVO底層原理

一、KVO基礎操作

添加觀察者

    [_myClass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    //屬性名
    NSLog(@"keyPath = %@",keyPath);
    //觀察屬性的對象,self
    NSLog(@"object = %@",object);
    //kind : 1 (新值) new : 屬性的新值
    //NSKeyValueChangeSetting = 1,改變的操作
    //NSKeyValueChangeInsertion = 2,插入的操作 數組
    //NSKeyValueChangeRemoval = 3,移除的操作 數組
    //NSKeyValueChangeReplacement = 4,代替的操作 數組
    NSLog(@"change = %@",change);
    NSLog(@"context = %@",context);
}

在dealloc中移除觀察者

    [_myClass removeObserver:self forKeyPath:@"name"];
`NSKeyValueObservingOptionNew`:把更改之后的值提供給處理方法

`NSKeyValueObservingOptionOld`:把更改之前的值提供給處理方法

`NSKeyValueObservingOptionInitial`:把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值。

`NSKeyValueObservingOptionPrior`:分2次調用。在值改變之前和值改變之后。

自動或者手動打開觀察者

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    //返回NO,手動來觸發,否則自動觸發。
    return YES;
}

如果設置成手動觸發,當要改變屬性值的時候需要加入如下代碼:

[_myClass willChangeValueForKey:@"name"];
[_myClass setValue:@"KVC" forKey:@"name"];
[_myClass didChangeValueForKey:@"name"];

二、底層實現:運行時替換ISA指針。

用黑魔法進行觀察

    NSLog(@"myClassBefore:%@",[self.myClass class]);
    NSLog(@"runtimeBefore:%@",object_getClass(self.myClass));
    [_myClass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    NSLog(@"myClassAfter:%@",[self.myClass class]);
    NSLog(@"runtimeAfter:%@",object_getClass(self.myClass));

打印結果:

myClassBefore:MyClass
runtimeBefore:MyClass
myClassAfter:MyClass
runtimeAfter:NSKVONotifying_MyClass

對myClass對象的name屬性添加觀察,runtime會改變myClass對象的類,MyClass->NSKVONotifying_MyClass。如何證明NSKVONotifying_MyClassMyClass的子類?加入頭文件#import <objc/runtime.h>,用runtime進行觀察。

- (void)viewDidLoad {
    [super viewDidLoad];   
    _myClass = [[MyClass alloc] init];
    NSLog(@"%@",[SecondController findSubClass:[_myClass class]]);
    [_myClass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    NSLog(@"%@",[SecondController findSubClass:[_myClass class]]);
}
+ (NSArray *)findSubClass:(Class)defaultClass{
    //獲取該類中所有類的個數
    int count = objc_getClassList(NULL, 0);
    if (count < 0) {
        return [NSArray array];
    }
    NSMutableArray * output = [NSMutableArray arrayWithObject:defaultClass];
    Class * classes = (Class *)malloc(sizeof(Class) * count);
    //獲取所有的類
    objc_getClassList(classes, count);   
    for (int i = 0; i < count; i ++) {
        if (defaultClass == class_getSuperclass(classes[i])) {
            [output addObject:classes[i]];
        }
    }
    free(classes);
    return output;
}

打印結果如下:

(
    MyClass
)
(
    MyClass,
    "NSKVONotifying_MyClass"
)

三、KVO對數組元素的監聽

    [_myClass addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew context:nil];
    //KVO監聽對數組中元素的變化
    [[_myClass mutableArrayValueForKey:@"arr"] addObject:@"add"];
    //這樣子添加是監聽不到的
    [_myClass.arr addObject:@"add"];

為什么用下面的方法不能監聽呢?KVO是基于KVC的,KVO能發送通知,都是通過KVC的方法處理的。
NSLog(@"%@",[[_myClass mutableArrayValueForKey:@"arr"] class]);會自動生成NSMutableArray的子類:NSKeyValueNotifyingMutableArray,并重寫了NSMutableArray相關的方法,如add,insert等(用黑魔法,runtime可以列出所有的方法),這里面添加了KVC的通信方法,當調用add這些方法的時候,就會觸發通知。在add放里面添加了willChangeValueForKey和didChangeValueForKey方法來觸發通知。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容