KVO簡單解析

  • 老司機們,快上車
  • 回到標題的內(nèi)容: KVO淺析
    • 先來看一下系統(tǒng)原生的效果:
#import "ViewController.h"
#import "LJModel.h"

@interface ViewController ()

@property (strong, nonatomic) LJModel *model;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LJModel *model = [[LJModel alloc]init];
    
    // 賦值
    _model = model;
    
    // 添加觀察者 ,監(jiān)聽model的 name 屬性的值的 改變
    [model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:@"ViewController"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
    NSLog(@"%@",_model.name);
    
    // 點擊三次的打印結(jié)果為:
    /*
     2016-07-12 12:56:31.453 answerDemo[2584:753037] 小強 1 號
     2016-07-12 12:56:40.509 answerDemo[2584:753037] 小強 2 號
     2016-07-12 12:56:42.195 answerDemo[2584:753037] 小強 3 號
     */
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 用于判斷每次點擊,都給name屬性賦值
    static int i = 0;
    
    i ++;
    
    // 通過set方法 給model的name賦值
    _model.name = [NSString stringWithFormat:@"小強 %d 號",i];
    
}

@end
  • 我們新建一個繼承自NSObjectLJModel

  • 給這個類添加一個name屬性@property (copy, nonatomic) NSString *name;

  • viewDidload的方法中,我們創(chuàng)建一個LJModel對象 model,并給全局的model屬性賦值

  • model添加一個觀察者(當前的ViewController控制器),用來監(jiān)聽name屬性的值的變化

  • 最后重寫(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context這個方法,并在這個方法里面輸出一下modelname屬性的值

  • 點擊三次屏幕,可以看到,每次name屬性的賦值都可以成功的被監(jiān)聽

這個是如何實現(xiàn)的呢?

  • 我們知道,要想監(jiān)聽某個值的改變,我們可以在set方法中去監(jiān)聽
  • 比如網(wǎng)絡請求,我們就可以在set方法中拿到請求回來的數(shù)據(jù)來刷新界面

那我們現(xiàn)在來做這樣一件事情:

#import <Foundation/Foundation.h>

@interface LJModel : NSObject

{
    @public
    NSString *_name;
}

@end
  • 我們不用@property修飾name,然后也不提供setget方法

  • 然后在點擊屏幕的時候,直接訪問model的成員變量_name進行賦值:

    // 直接訪問成員變量進行賦值
    _model -> _name = [NSString stringWithFormat:@"小強 %d 號",i];
  • 我們再在(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context打印_name:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
    NSLog(@"%@",_model -> _name);

}
  • 此時再點擊屏幕,debug沒任何輸出,這個時候觀察者ViewController就監(jiān)聽不到_name的值得變化了

  • 然后我們把model的_name改成用@property修飾的name

  • 這時候系統(tǒng)就會自動生成_name屬性,并且生成getset方法,我們就可以通過點語法賦值和取值了

  • 現(xiàn)在的問題就是,我們沒有重寫model的setName方法,ViewController是怎么通過set方法監(jiān)聽的呢

    • 憋說話,看圖:
isa指向LJModel.png
isa指向NSKVONotifying_LJModel.png
  • 我們在給model添加觀察者的地方打個斷點,上面兩張圖是單步調(diào)試之后的結(jié)果

  • 我們可以看到?jīng)]給model添加觀察者之前,isa(暫時先簡單的理解成指針,這里不做探討)指針指向的是LJModel這個類

  • 添加觀察者之后,isa指針指向了NSKVONotifying_LJModel,這TM是個什么鬼

  • 我們在創(chuàng)建類的時候,系統(tǒng)都會自動自動生成一個isa指針,指向這個類,當這個類或這個類的實例化對象調(diào)用這個方法的時候,會先根據(jù)isa找到對應的類,來判斷有沒有這個方法,有則調(diào)用,沒有就會報錯

  • 顯然在這里,系統(tǒng)修改了我們的isa指針,那我們在調(diào)用modelsetName方法就會去NSKVONotifying_LJModel里面找,然后這里又沒有報錯,這說明NSKVONotifying_LJModel這個類里面有setName方法,并且對modelname屬性成功的監(jiān)聽

  • 想要監(jiān)聽modelname的值得改變,我們可以在model里面重寫setName方法,但是這里系統(tǒng)已經(jīng)將isa指針指向了NSKVONotifying_LJModel這個類,并不是LJModel這個類

  • 這時候,我們可以大概猜測一下NSKVONotifying_LJModel這個類是LJModel的分類或者子類,因為LJModel的分類或者子類可以重寫setName方法, 然后系統(tǒng)重寫了setName方法,成功的對name的值得改變進行監(jiān)聽

  • 但是又因為分類重寫方法,系統(tǒng)會優(yōu)先調(diào)用分類的方法,這樣就會覆蓋原有類的方法,如果原有類重寫了setName,那被分類覆蓋掉之后,導致重寫失敗,KVO只是監(jiān)聽值得改變,并不會覆蓋掉原有類的方法,所以,這個類應該是子類,然后在子類重寫父類的方法,并且調(diào)用[super setName]方法,從而在不影響父類的值得情況下,對父類進行監(jiān)聽

到這里之后,我們嘗試的寫了一下,簡單的仿照系統(tǒng),自己搞個可以監(jiān)聽name的值得變化的方法:

  • 開始我們先來看一下系統(tǒng)的.com + 左鍵到系統(tǒng)的方法里面去,進去之前,系統(tǒng)會提示有三個方法,先隨便選一個,然后找到NSObject(NSKeyValueObserverRegistration):
@interface NSObject(NSKeyValueObserverRegistration)
- (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 NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end
  • 可以看到,這里面有好幾個分類,都有這個方法,這里因為LJModel是直接繼承NSObject的,所以,model在調(diào)用這個方法的時候,會選擇NSObject的分類的方法
  • 既然這樣,我們就com + Ccom + V

  • 然后我們也搞一個這樣的方法,為了和系統(tǒng)的方法區(qū)分開來,我們給方法改個名字,加上自己的前綴:

#import "NSObject+Extension.h"
#import <objc/runtime.h>
#import "LJSubModel.h"

@implementation NSObject (Extension)

- (void)lj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    
    
    // 將當前對象的指針指向觀察者
    object_setClass(self, [LJSubModel class]);

    // 給當前對象添加一個觀察者(ViewController)的屬性
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

@end
  • 我們仿照系統(tǒng),創(chuàng)建NSObject的分類和一個LJModel的子類
  • 在分類的這個方法里面,將當前類的isa指向子類,這樣,model調(diào)用setName就會到subModel里面掉setName方法
  • 然后將觀察者動態(tài)添加到subModel的屬性

然后看一下subModelsetName方法的實現(xiàn):

#import "LJSubModel.h"
#import <objc/runtime.h>

@implementation LJSubModel

- (void)setName:(NSString *)name {
    [super setName:name];
    
    // 獲取觀察者
    id observer = objc_getAssociatedObject(self, @"observer");
    
    // 通知觀察者調(diào)用方法
    [observer observeValueForKeyPath:@"name" ofObject:observer change:nil context:nil];
}

@end

最后調(diào)用自己的方法實現(xiàn)監(jiān)聽:

    // 添加觀察者 ,監(jiān)聽model的 name 屬性的值的 改變
    [model lj_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  • 首先我們調(diào)用[super setName:name];,這樣就可以給model.name賦值了

  • 然后拿到觀察者,并手動調(diào)用[observer observeValueForKeyPath:@"name" ofObject:self change:nil context:nil]; }

  • 這里就相當于外面viewController調(diào)用這個方法,并將ViewController作為作為觀察者

  • 這樣,我們在ViewController里面用model調(diào)用我自己的方法, 然后實現(xiàn)obserVerForKeyPath的時候,就可以監(jiān)聽到name的值得改變了

  • 這時候,我們再來看一下:

isa指向SubModel.png

這樣,用我們自己的方法,也可以實現(xiàn)簡單的KVO了

KVO很強大,有很多值的我們探究的地方,這里只是簡單的介紹了一下KVO的實現(xiàn)機制,希望能幫到大家

demo傳送門:https://github.com/lauding/answerDemo

另外附上一篇個人覺得比較好的介紹KVO的實現(xiàn)的博客:http://www.cocoachina.com/ios/20150313/11321.html

對我的Demo有什么疑問或者建議,歡迎留言

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稍算,一起剝皮案震驚了整個濱河市犹菇,隨后出現(xiàn)的幾起案子溪王,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亭枷,死亡現(xiàn)場離奇詭異疤剑,居然都是意外死亡,警方通過查閱死者的電腦和手機捻勉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刀森,“玉大人踱启,你說我怎么就攤上這事⊙械祝” “怎么了埠偿?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榜晦。 經(jīng)常有香客問我冠蒋,道長,這世上最難降的妖魔是什么乾胶? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任抖剿,我火速辦了婚禮,結(jié)果婚禮上识窿,老公的妹妹穿的比我還像新娘斩郎。我一直安慰自己,他們只是感情好喻频,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布缩宜。 她就那樣靜靜地躺著,像睡著了一般甥温。 火紅的嫁衣襯著肌膚如雪锻煌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天姻蚓,我揣著相機與錄音宋梧,去河邊找鬼。 笑死史简,一個胖子當著我的面吹牛乃秀,可吹牛的內(nèi)容都是我干的肛著。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼跺讯,長吁一口氣:“原來是場噩夢啊……” “哼枢贿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刀脏,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤局荚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后愈污,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耀态,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年暂雹,在試婚紗的時候發(fā)現(xiàn)自己被綠了首装。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡杭跪,死狀恐怖仙逻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涧尿,我是刑警寧澤系奉,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站姑廉,受9級特大地震影響缺亮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桥言,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一萌踱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧限书,春花似錦虫蝶、人聲如沸章咧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赁严。三九已至扰柠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疼约,已是汗流浹背卤档。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留程剥,地道東北人劝枣。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親舔腾。 傳聞我的和親對象是個殘疾皇子溪胶,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容