iOS KVO本質(zhì)探索

KVO全稱為Key Value Observing井辜,鍵值監(jiān)聽機制,由NSKeyValueObserving協(xié)議提供支持管闷,NSObject類繼承了該協(xié)議粥脚,所以NSObject的子類都可使用該方法。

KVO使用步驟

1.注冊觀察者(為被觀察這指定觀察者以及被觀察者屬性)
創(chuàng)建一個Person對象包个,寫一個age屬性刷允,為age屬性添加KVO監(jiān)聽

/* 
options: 有4個值,分別是:
NSKeyValueObservingOptionOld 把更改之前的值提供給處理方法 
NSKeyValueObservingOptionNew 把更改之后的值提供給處理方法 
NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法碧囊,一旦注冊树灶,立馬就會調(diào)用一次。通常它會帶有新值糯而,而不會帶有舊值天通。 
NSKeyValueObservingOptionPrior 分2次調(diào)用。在值改變之前和值改變之后熄驼。 
 */
//注冊一個監(jiān)聽器用于監(jiān)聽指定的key路徑
[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"];

2.實現(xiàn)回調(diào)方法

// 當監(jiān)聽對象的屬性值發(fā)生改變時像寒,就會調(diào)用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"監(jiān)聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context);
}

3.修改需要監(jiān)聽的屬性值烘豹,查看是否監(jiān)聽成功

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person1.age = 10;
}
使用注意點

[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"] ;這行代碼中context:@"123"這個參數(shù)傳的值都會
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{}傳到回調(diào)方法中context中來,用途: 如果說在一個控制器中我們監(jiān)聽了多個屬性的變化诺祸,而每一個屬性的變化之后對應(yīng)的事件處理也是不一樣的携悯,那么在注冊觀察者時context 傳不同的字符串就可以區(qū)分判斷了

思考:為什么注冊觀察者之后,修改觀察對象屬性的值筷笨,就會走回調(diào)方法憔鬼?KVO是怎么實現(xiàn)這個功能的呢?
帶著這兩個問題下面來一探究竟
  • OC是消失機制當我們調(diào)用 self.person1.age = 10; 這行代碼時實際就是[self.person setAge:10] 然后再轉(zhuǎn)化成objc_msgsend(person,@selector(setAge)),那么是不是KVO在setAge 這個方法中做了文章呢胃夏?前幾篇文章已經(jīng)寫到對象方法存儲到類對象中轴或,那么在控制臺輸入 po self.person.isa 發(fā)現(xiàn)結(jié)果并不是 Person 而是 NSKVONotifying_Person 這說明KVO在運行時新建了一個NSKVONotifying_Person類,將person的isa指針指向這個類, 在控制臺輸入 po [self.person class]發(fā)現(xiàn)輸出的是Person而不是NSKVONotifying_Person。這是為什么仰禀?

猜測:系統(tǒng)在運行時照雁,KVO動態(tài)創(chuàng)建一個NSKVONotifying_Person類,將person的isa指針指向這個類悼瘾。新創(chuàng)建的NSKVONotifying_Person 繼承與Person 并且重寫了 setAge方法和 class方法

驗證1:NSKVONotifying_Person是否繼承自Person囊榜,是否重寫了Class方法
#import <objc/runtime.h>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person1.age = 20;
    //獲取類對象
    Class personClass = object_getClass(self.person1);
    //通過類對象獲取父類
    Class personClass02 = class_getSuperclass(personClass);
    NSLog(@"%@-------%@",personClass02,personClass03);
}
輸出 NSKVONotifying_Person-------Person

通過這幾行代碼就可以證明NSKVONotifying_Person 繼承自 Person

  • 細節(jié)
    再獲取類對象時可以使用object_getClass 也可以使用[A class],上面為什么使用了object_getClass而不是使用[A class]呢?
    答案: NSKVONotifying_Person 重寫了class方法亥宿,[person class]返回的并不是原對象而是原對象的父類也就是Person類卸勺,如果沒有重寫的話,返回person的isa指針指向的類打印結(jié)果應(yīng)該是NSKVONotifyin_Person烫扼,但是蘋果官方不希望將NSKVONotifyin_Person類的內(nèi)部實現(xiàn)暴露出來曙求,所以在內(nèi)部重寫了class方法,直接返回Person類映企,所以我們在調(diào)用person的class方法時悟狱,返回的是Person類。
驗證2:NSKVONotifying_Person是否重寫了setAge方法
  //新建另外一個person2對象堰氓,不添加監(jiān)聽
 self.person2 = [[Person alloc] init];
 self.person2.age = 2;

//點擊修改person2的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.age = 20;
    Class personClass = object_getClass(self.person);
    Class personClass02 = object_getClass(personClass);
    Class personClass03 = class_getSuperclass(personClass);
    
    //修改person2的值
    self.person2.age = 20;
}

答案:person2 在調(diào)用setAge方法時并沒有觸發(fā)回調(diào)方法挤渐,而添加了觀察者的person對象在調(diào)用setAge 方法時觸發(fā)了方法,說明NSKVONotifying_Person 重寫了setAge方法

思考1:NSKVONotifying_Person怎么重寫了setAge方法以實現(xiàn)觸發(fā)回調(diào)

經(jīng)過查看底層源碼和相關(guān)資料分析們可以知道双絮,NSKVONotifyin_Person中的setAge方法中其實調(diào)用了Fundation框架中C語言函數(shù)_NSsetIntValueAndNotify浴麻,而_NSsetIntValueAndNotify內(nèi)部做的操作相當于,首先調(diào)用willChangeValueForKey方法囤攀,之后調(diào)用父類的setAge方法對成員變量賦值软免,最后調(diào)用didChangeValueForKey方法。其中didChangeValueForKey中會調(diào)用監(jiān)聽器的監(jiān)聽方法焚挠,最終來到監(jiān)聽者的observeValueForKeyPath方法膏萧。

思考2:NSKVONotifyin_Person的內(nèi)部結(jié)構(gòu)是怎樣的?

NSKVONotifyin_Person作為Person的子類,其superclass指針指向Person類榛泛,并且NSKVONotifyin_Person內(nèi)部的setAge方法做了單獨的實現(xiàn)蝌蹂。我們可以通過runtime的方法去分別打印person1person2兩個對象和NSKVONotifyin_Person類對象內(nèi)存儲的對象方法:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.person1 = [[Person alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[Person alloc] init];
    self.person2.age = 2;
// 給person1對象添加KVO監(jiān)聽
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];

    [self printMethods: object_getClass(self.person1)];
    [self printMethods: object_getClass(self.person2)];

    [self.person1 removeObserver:self forKeyPath:@"age"];
}

- (void) printMethods:(Class)cls
{
    unsigned int count ;
    Method *methods = class_copyMethodList(cls, &count);
    NSMutableString *methodNames = [NSMutableString string];
    [methodNames appendFormat:@"%@ : ", cls];
    
    for (int i = 0 ; i < count; i++) {
        Method method = methods[I];
        NSString *methodName  = NSStringFromSelector(method_getName(method));
        
        [methodNames appendString: methodName];
        [methodNames appendString:@" "];
        
    }
    
    NSLog(@"%@",methodNames);
    free(methods);
}

打印輸出:

Person : setAge:, age,
NSKVONotifying_Person : setAge:, class, dealloc, _isKVOA,

NSKVONotifyin_Person的內(nèi)存結(jié)構(gòu)及方法調(diào)用順序


NSKVONotifyin_Person的內(nèi)存結(jié)構(gòu)及方法調(diào)用順序圖解
總結(jié):

1、KVO的本質(zhì)是什么挟鸠?
當我們給對象注冊一個觀察者添加了KVO監(jiān)聽時叉信,系統(tǒng)會修改這個對象的isa指針指向亩冬。在運行時艘希,動態(tài)創(chuàng)建一個新的子類,NSKVONotifying_A類硅急,將A的isa指針指向這個子類覆享,來重寫原來類的set方法;set方法實現(xiàn)內(nèi)部會順序調(diào)用willChangeValueForKey方法营袜、原來的setter方法實現(xiàn)撒顿、didChangeValueForKey方法,而didChangeValueForKey方法內(nèi)部又會調(diào)用監(jiān)聽器的observeValueForKeyPath:ofObject:change:context:監(jiān)聽方法荚板。
2凤壁、如何手動觸發(fā)KVO?
實現(xiàn)調(diào)用willChangeValueForKey和didChangeValueForKey方法跪另。

如有疑問:
iOS OC對象的本質(zhì)窺探(一)
iOS OC對象的本質(zhì)窺探(對象分類)(二)

特別推薦:
iOS獲取手機唯一標示
iOS 高德地圖實現(xiàn)大頭針展示拧抖,分級大頭針,自定制大頭針免绿,在地圖上畫線唧席,線和點共存,路線規(guī)劃(駕車路線規(guī)劃)嘲驾,路線導(dǎo)航淌哟,等一些常見的使用場景

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辽故,隨后出現(xiàn)的幾起案子徒仓,更是在濱河造成了極大的恐慌,老刑警劉巖誊垢,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掉弛,死亡現(xiàn)場離奇詭異,居然都是意外死亡彤枢,警方通過查閱死者的電腦和手機狰晚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缴啡,“玉大人壁晒,你說我怎么就攤上這事∫嫡ぃ” “怎么了秒咐?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵谬晕,是天一觀的道長。 經(jīng)常有香客問我携取,道長攒钳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任雷滋,我火速辦了婚禮不撑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晤斩。我一直安慰自己焕檬,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布澳泵。 她就那樣靜靜地躺著实愚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兔辅。 梳的紋絲不亂的頭發(fā)上腊敲,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音维苔,去河邊找鬼碰辅。 笑死,一個胖子當著我的面吹牛蕉鸳,可吹牛的內(nèi)容都是我干的乎赴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼潮尝,長吁一口氣:“原來是場噩夢啊……” “哼榕吼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起勉失,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羹蚣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乱凿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顽素,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年徒蟆,在試婚紗的時候發(fā)現(xiàn)自己被綠了胁出。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡段审,死狀恐怖全蝶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤抑淫,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布绷落,位于F島的核電站,受9級特大地震影響始苇,放射性物質(zhì)發(fā)生泄漏砌烁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一催式、第九天 我趴在偏房一處隱蔽的房頂上張望函喉。 院中可真熱鬧,春花似錦蓄氧、人聲如沸函似。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至顿天,卻和暖如春堂氯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牌废。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工咽白, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸟缕。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓晶框,卻偏偏與公主長得像,于是被迫代替她去往敵國和親懂从。 傳聞我的和親對象是個殘疾皇子授段,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

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

  • 問題 iOS用什么方式實現(xiàn)對一個對象的KVO?(KVO的本質(zhì)是什么番甩?) 如何手動觸發(fā)KVO 侵贵? 首先需要了解KVO...
    hjltony閱讀 579評論 0 2
  • 什么是KVO? KVO 全稱 Key Value Observing缘薛,是蘋果提供的一套事件通知機制窍育。允許對象監(jiān)聽另...
    祀夢_閱讀 489評論 0 4
  • 什么是KVO? KVO 全稱 Key Value Observing宴胧,是蘋果提供的一套事件通知機制漱抓。允許對象監(jiān)聽另...
    薩繆閱讀 7,647評論 0 17
  • 什么是KVO? KVO 全稱 Key Value Observing恕齐,是蘋果提供的一套事件通知機制乞娄。允許對象監(jiān)聽另...
    薩繆閱讀 561評論 0 1
  • 面試問題: · iOS用什么方式實現(xiàn)對一個對象的KVO? · 如何手動觸發(fā)KVO? 我們通過以下幾個點來尋找這兩個...
    高思陽閱讀 239評論 0 1