探索iOS底層原理第二篇——KVO

本系列是學(xué)習(xí)iOS底層原理過程中的記錄筆記第二篇,第一篇在這里:
探索iOS底層原理開篇——對(duì)象本質(zhì)
首先拋出三個(gè)面試題:

  • iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO膊夹?(KVO的本質(zhì)是什么坏怪?)
  • 如何手動(dòng)觸發(fā)KVO?
  • 直接修改成員變量會(huì)觸發(fā)KVO么?

KVO的全稱是Key-Value Observing铛楣,俗稱“鍵值監(jiān)聽”,可以用于監(jiān)聽某個(gè)對(duì)象屬性值的改變
使用方法:
新建Person類艺普,一個(gè)age成員變量簸州,給Person的age添加KVO監(jiān)聽
當(dāng)前控制器類添加name屬性,給self的name添加KVO監(jiān)聽

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

[self addObserver:self forKeyPath:@"name" options:options context:@"456"];

//修改age值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person.age = 21;
    [self.person setAge:22];

    self.name = @"05241";
    _name = @"890";
}
// 當(dāng)監(jiān)聽對(duì)象的屬性值發(fā)生改變時(shí)歧譬,就會(huì)調(diào)用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"監(jiān)聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context);
}

打印結(jié)果如下:
image.png

可以看出只要是通過set方法修改成員變量都會(huì)觸發(fā)KVO監(jiān)聽岸浑,直接下劃線訪問成員變量不會(huì)觸發(fā)KVO。
我們的都是不管是self.person.age還是[self.person setAge]本質(zhì)都是調(diào)用Person類的setAge方法瑰步,在上一篇我們一直到調(diào)用對(duì)象方法的本質(zhì)是通過isa找到類對(duì)象的方法列表矢洲,因此我們?cè)囍鴮?duì)比兩個(gè)Person的類對(duì)象有什么不同,一個(gè)有添加KVO監(jiān)聽缩焦,一個(gè)沒有读虏,因?yàn)檎G闆r下,Person的類對(duì)象永遠(yuǎn)都是同一個(gè)袁滥。詳細(xì)可參考我上一篇:探索iOS底層原理——對(duì)象本質(zhì)
我們打印兩個(gè)person的isa發(fā)現(xiàn)person的類對(duì)象確實(shí)不同:

image.png

添加KVO的person1的類對(duì)象是NSKVONotifying_Person盖桥,我們繼續(xù)分析,前面說了更改屬性值實(shí)際上是調(diào)用了setAge方法题翻,我們打印一下person在添加KVO監(jiān)聽前后的setAge方法的地址揩徊,看一下他底層到底是調(diào)用了什么方法,因?yàn)榍懊娣治隽颂砑覭VO后,Person類的isa實(shí)際上是指向了NSKVONotifying_Person類靴拱,這是Runtime在運(yùn)行時(shí)動(dòng)態(tài)添加的類垃喊,因?yàn)槲覀兛梢圆孪雽?shí)際上是調(diào)用了NSKVONotifying_Person的setAge方法,在這個(gè)方法里面再調(diào)用了KVO的方法通知KVO觸發(fā)的方法observeValueForKeyPath: ofObject: change: context:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[Person alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[Person alloc] init];
    self.person2.age = 2;
    

    NSLog(@"person1添加KVO監(jiān)聽之前 - person1:%p    person2:%p",
           [self.person1 methodForSelector:@selector(setAge:)],
           [self.person2 methodForSelector:@selector(setAge:)]);
    
    // 給person1對(duì)象添加KVO監(jiān)聽
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    
    
    NSLog(@"person1添加KVO監(jiān)聽之后 - person1: %p     person2:%p",
          [self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
}

打印結(jié)果如下:

image.png

結(jié)果符合我們的猜想袜炕,Person在添加KVO之后本谜,類對(duì)象NSKVONotifying_Person調(diào)用setAge方法實(shí)際上是調(diào)用了Foundation框架的_NSSetIntValueAndNotify方法,由于Apple上的Foundation不開源偎窘,我們無法直接查看_NSSetIntValueAndNotify方法的具體實(shí)現(xiàn)乌助,不過可以通過越獄手機(jī)查找系統(tǒng)目錄的Foundation框架可執(zhí)行文件,再通過hopper反編譯工具查看Foundation的匯編實(shí)現(xiàn)陌知,這個(gè)不在本文討論之內(nèi)他托,在之后總結(jié)逆向知識(shí)的時(shí)候再分析。這里直接給出答案(偽代碼實(shí)現(xiàn)):

void _NSSetIntValueAndNotify()
{
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

_NSSetIntValueAndNotify方法實(shí)際上會(huì)先調(diào)用willChangeValueForKey方法仆葡,然后再調(diào)回父類PersonsetAge方法赏参,再調(diào)用didChangeValueForKey,這個(gè)方法內(nèi)部實(shí)現(xiàn)最終會(huì)調(diào)用observeValueForKeyPath: ofObject: change: context:觸發(fā)監(jiān)聽方法沿盅,最終我們就可以在這個(gè)方法收到KVO更新的通知了:

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

既然添加KVO之后調(diào)用set方法實(shí)際上是調(diào)用** willChangeValueForKey和didChangeValueForKey**方法,我們?cè)囍煌ㄟ^setAge觸發(fā)KVO腰涧,試著調(diào)用這兩個(gè)方法看能不能成功觸發(fā)KVO:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    [self.person1 setAge:21];
    
    [self.person1 willChangeValueForKey:@"age"];
    [self.person1 didChangeValueForKey:@"age"];
}

當(dāng)點(diǎn)擊后韧掩,發(fā)現(xiàn)確實(shí)能成功收到觸發(fā)KVO!


image.png

至此窖铡,我們分析完畢疗锐!

總結(jié):

一.iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?(KVO的本質(zhì)是什么费彼?)

  • 利用RuntimeAPI動(dòng)態(tài)生成一個(gè)子類滑臊,并且讓instance對(duì)象的isa指向這個(gè)全新的子類
  • 當(dāng)修改instance對(duì)象的屬性時(shí),會(huì)調(diào)用Foundation的_NSSetXXXValueAndNotify函數(shù)
    1. willChangeValueForKey:
      父類原來的setter
    2. didChangeValueForKey:
    3. 內(nèi)部會(huì)觸發(fā)監(jiān)聽器(Oberser)的監(jiān)聽方法( observeValueForKeyPath:ofObject:change:context:)

二.如何手動(dòng)觸發(fā)KVO敌买?

  • 手動(dòng)調(diào)用willChangeValueForKey:和didChangeValueForKey:

三.直接修改成員變量會(huì)觸發(fā)KVO么简珠?
不會(huì)觸發(fā)KVO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市虹钮,隨后出現(xiàn)的幾起案子聋庵,更是在濱河造成了極大的恐慌,老刑警劉巖芙粱,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祭玉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡春畔,警方通過查閱死者的電腦和手機(jī)脱货,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門岛都,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人振峻,你說我怎么就攤上這事臼疫。” “怎么了扣孟?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵烫堤,是天一觀的道長。 經(jīng)常有香客問我凤价,道長鸽斟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任利诺,我火速辦了婚禮富蓄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慢逾。我一直安慰自己立倍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布氛改。 她就那樣靜靜地躺著帐萎,像睡著了一般比伏。 火紅的嫁衣襯著肌膚如雪胜卤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天赁项,我揣著相機(jī)與錄音葛躏,去河邊找鬼。 笑死悠菜,一個(gè)胖子當(dāng)著我的面吹牛舰攒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播悔醋,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼摩窃,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了芬骄?” 一聲冷哼從身側(cè)響起猾愿,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎账阻,沒想到半個(gè)月后蒂秘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淘太,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年姻僧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了规丽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撇贺,死狀恐怖赌莺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情松嘶,我是刑警寧澤雄嚣,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站喘蟆,受9級(jí)特大地震影響缓升,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蕴轨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一港谊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧橙弱,春花似錦歧寺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蛀缝,卻和暖如春顷链,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屈梁。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工嗤练, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人在讶。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓煞抬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親构哺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子革答,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359