iOS之KVO實現(xiàn)原理探究

KVO,就是key-value-observing,鍵值觀察者模式。開發(fā)中經(jīng)常會使用到,并且面試大概率問到其底層實現(xiàn)原理。

用法

eg:

#import <Foundation/Foundation.h>
@interface Programmer : NSObject
@property (copy, nonatomic) NSString *name;
@end

#import "Programmer.h"
@implementation Programmer
- (void)setName:(NSString *)name {
    _name = name;
}
@end

#import "ViewController.h"
#import "Programmer.h"
#import <objc/runtime.h>

@interface ViewController ()

@property (strong, nonatomic) Programmer *programmer1;
@property (strong, nonatomic) Programmer *programmer2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.programmer1 = [[Programmer alloc] init];
    self.programmer1.name = @"Bob";
    self.programmer2 = [[Programmer alloc] init];
    self.programmer2.name = @"Lili";
    
    NSLog(@"添加監(jiān)聽之前person1的isa指針指向%@",object_getClass(self.programmer1));//在調(diào)試模式下可以直接 po self.person1->isa
    NSLog(@"添加監(jiān)聽之前person類對象的isa指針指向%@",object_getClass(object_getClass(self.programmer1)));
    NSLog(@"添加監(jiān)聽之前person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);//在調(diào)試模式下,通過 p (IMP)地址 打印出這個IMP地址對應(yīng)的方法名稱
    
    [self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    NSLog(@"添加監(jiān)聽之后person1的isa指針指向%@",object_getClass(self.programmer1));
    NSLog(@"添加監(jiān)聽之后person類對象的isa指針指向%@",object_getClass(object_getClass(self.programmer1)));
    NSLog(@"添加監(jiān)聽之后person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.programmer1.name = @"Mark";
    self.programmer2.name = @"Jeny";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"keyPath===%@",keyPath);
    NSLog(@"object===%@",object);
    NSLog(@"change===%@",change);
    NSLog(@"context===%@",context);
}
  1. 定義一個Programmer類,有一個name的property
  2. 然后在控制器中有一個@property (strong, nonatomic) Programmer *programmer1;
  3. 在ViewDidLoad中添加觀察,[self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];,NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld分別表示變化之前的值和改變之后的值
  4. 在監(jiān)聽的控制器中實現(xiàn)- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context這個方法,其中change為一個字典,里面包含新值和舊值

原理

上面的例子中:

  1. Programmer類的實例programmer1name屬性被控制器監(jiān)聽了。這時,OC的runtime機制生成了一個KVONotifying_ Programmer的類
  2. programmer1實例的isa指針從指向Programmer的類對象,變成指向KVONotifying_ Programmer的類對象,而KVONotifying_ Programmer的isa指針指的是KVONotifying_ Programmer的meta-class元類對象,KVONotifying_ Programmer的superclass指針指的是Programmer的類對象
  3. 修改programmer1name屬性的時候調(diào)用了Foundation框架下的一個_NSSetObjectValueAndNotify方法
  4. KVONotifying_ Programmer類重寫了Programmer類屬性name的setter方法加入了NSObject的兩個方法:willChangeValueForKey:(值改變之前)和didChangevlueForKey:(值改變之后)。在一個被觀察屬性發(fā)生改變之前,willChangeValueForKey:一定會被調(diào)用,這就會記錄舊的值。而當改變發(fā)生后,didChangeValueForKey:會被調(diào)用,繼而observeValueForKey:ofObject:change:context:也會被調(diào)用。

探究

待續(xù)

如果有錯誤,感謝各位大佬指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容