iOS底層探索之KVO(二)—KVO原理分析

回顧

上一篇博客中善茎,已經(jīng)介紹了KVO的相關(guān)操作毫缆,那么接下來就去探索一下KVO的底層邏輯悼吱,KVO到底是如何實現(xiàn)的呢堰酿?

文章主題

  • 在官方文檔中有如下圖中的說明


    isa-swizzling

鍵值觀察是使用稱為isa-swizzling的技術(shù)實現(xiàn)的。

  • isa指針昭卓,顧名思義愤钾,指向?qū)ο蟮念悾3忠粋€調(diào)度表候醒。該調(diào)度表主要包含指向類實現(xiàn)的方法的指針绰垂,以及其他數(shù)據(jù)。

  • 當觀察者為對象的屬性注冊時火焰,被觀察對象的 isa指針被修改劲装,指向中間類不是真正的類。因此,isa指針的值不一定反映實例的實際類占业。

  • 你不應該依賴isa指針來確定類的成員绒怨。相反,應該使用該class方法來確定實例對象的類谦疾。

1. isa-swizzling驗證

在添加觀察者處南蹂,打上斷點,再控制臺lldb調(diào)試看看念恍。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.student = [[JPStudent alloc]init];
    [self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
}
  • 控制臺打印如下


    lldb調(diào)試

addObserverstudentJPStudent變成了NSKVONotifying_JPStudent

我們知道六剥,實例對象的關(guān)系實際上就是實例對象的isa指向了類對象。所以這里我們可以推斷峰伙,self.student在調(diào)用addObserver方法后疗疟,已經(jīng)從JPStudent類的實例對象,變成了NSKVONotifying_JPStudent
的實例對象瞳氓。

2. NSKVONotifying_JPStudent子類驗證

  • 那么這個NSKVONotifying_JPStudent是什么東西呢策彤?是一開始就直接存在,還是和JPStudent類之間有什么關(guān)系呢匣摘?那么看看NSKVONotifying_JPStudent是不是一開始就存在的店诗,再次運行代碼,斷點還是斷在添加觀察者者處音榜,打印一下
    NSKVONotifying_JPStudent生產(chǎn)測試

提醒objc_getClassruntimeapi庞瘸,一定要導入頭文件才可以正常使用,如圖所示赠叼。

在調(diào)用addObserver方法前后分別打印擦囊,結(jié)果說明NSKVONotifying_JPStudent是系統(tǒng)動態(tài)生成添加的一個類。這兩個類名字這么相似梅割,有沒有可能是JPStudent的子類呢霜第?我們打印一下看看

驗證NSKVONotifying_JPStudent是JPStudent的子類

從打印來看葛家,發(fā)現(xiàn)了新大陸户辞,NSKVONotifying_JPStudent 確實是繼承自JPStudent的。那么這個中間類癞谒,有沒有可能存在自己的子類呢底燎?我們通過下面這段代碼來看看

#pragma mark - 遍歷類以及子類
- (void)printClasses:(Class)cls{
     // 注冊類的總數(shù)
     int count = objc_getClassList(NULL, 0);
     // 創(chuàng)建一個數(shù)組, 其中包含給定對象
     NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
     // 獲取所有已注冊的類
     Class* classes = (Class*)malloc(sizeof(Class)*count);
     objc_getClassList(classes, count);
     for (int i = 0; i<count; i++) {
          if (cls == class_getSuperclass(classes[i])) {
                [mArray addObject:classes[i]];
          }
     }
     free(classes);
     NSLog(@"classes = %@", mArray);
}
  • 打印結(jié)果如下
    遍歷類以及子類

    從打印來看弹砚,可以驗證NSKVONotifying_JPStudentJPStudent的子類双仍。
    那么NSKVONotifying_JPStudent這個類里面都有些什么內(nèi)容呢?類里面一般也就是存儲了成員變量桌吃、方法朱沃、協(xié)議等信息,那么通過下面這段代碼來看看它里面都有什么。
#pragma mark **- 遍歷方法-ivar-property**
- (void)printClassAllMethod:(Class)cls{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
}
  • 打印如下


    遍歷方法-ivar-property

從打印結(jié)果來看逗物,系統(tǒng)重寫了setNickName搬卒、classdealloc這幾個方法翎卓,并且添加了一個叫_isKVOA的方法契邀,來區(qū)分是不是系統(tǒng)通過KVO自動生成的。

3. 觀察者被移除isa的指向失暴?

官方文檔中說:調(diào)用addObserver方法會修改isa指向坯门,那么現(xiàn)在我們移除觀察者后系統(tǒng)會怎么做呢?我們在dealloc方法中移除觀察者這里打上斷點逗扒,然后繼續(xù)觀察self.studentisa指向古戴。

觀察者被移除isa的指向?

移除觀察者之后缴阎,self.studentisa又指回了JPStudent類允瞧。并且生成的子類NSKVONotifying_JPStudent 還在,沒有進行銷毀蛮拔。

原因是如果下次繼續(xù)進行觀察者添加述暂,系統(tǒng)就不會再生成新的中間類,而是直接使用這個類了建炫,防止資源的浪費畦韭。

4. class方法

在添加觀察者之后,我們都知道會生成動態(tài)子類NSKVONotifying_JPStudent 肛跌,

那么調(diào)用class方法p self.student.class打印的是 NSKVONotifying_JPStudent 嗎艺配??衍慎?

  • 斷點在添加觀察者之后转唉,我們控制臺驗證一下
    self.student.class

    從打印結(jié)果看,輸出的還是JPStudent稳捆,雖然self.studentisa已經(jīng)指向NSKVONotifying_JPStudent了赠法,但是由于NSKVONotifying_JPStudent重寫了class方法,最后打印輸出的還是JPStudent 乔夯,蘋果這么做的目的是為了隱藏系統(tǒng)在背后做的一系列操作砖织,讓開發(fā)者更少的關(guān)注底層邏輯,只關(guān)注上層的代碼實現(xiàn)就可以末荐。

5. setter方法

既然重寫了setter方法觀察屬性侧纯,那么如果有成員變量,是否也可以能觀察呢甲脏?增加age成員變量眶熬,測試一下

@interface JPStudent : NSObject
{
    @public
    int age;
}
@property (nonatomic, copy) NSString *name;

@end 
setter方法測試

當對age進行賦值的時候妹笆,并沒有觸發(fā)監(jiān)聽的回調(diào)方法。那么就說明了只是對屬性的setter方法進行的監(jiān)聽娜氏。

我們再看看在dealloc中觀察者移除isa指回的時候晾浴,查看name的值

觀察者移除后Name的值

那就說明在KVO生成的類中對name的修改影響到了原始類。

name下個內(nèi)存斷點調(diào)試看看

斷點調(diào)試

  • bt 打印堆棧信息


    bt 打印堆棧信息

發(fā)現(xiàn)調(diào)用了Foundation的一些方法牍白,最后才是[JPStudent setName:]name賦值脊凰。

提示Foundation框架是不開源的。

  • Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
  • Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
  • Foundation`_NSSetObjectValueAndNotify

_NSSetObjectValueAndNotify匯編調(diào)用主要如下:

_NSSetObjectValueAndNotify
"willChangeValueForKey:"
這里是調(diào)用setter方法賦值
"didChangeValueForKey:"
"_changeValueForKey:key:key:usingBlock:"

從堆棧信息和匯編可以知道茂腥,在_changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:中進行賦值后的回調(diào)狸涌,那么肯定得通知監(jiān)聽者

  • observeValueForKeyPath的回調(diào)中打個斷點:
在這里插入圖片描述

確認是在NSKeyValueNotifyObserver通知中進行的回調(diào)。

6.總結(jié)

  • KVO添加觀察者addObserver動態(tài)生成子類NSKVONotifying_XXX最岗。
  • 重寫class方法帕胆,返回父類class信息。父類isa指向子類般渡。
    給動態(tài)子類添加setter方法(所有要觀察的屬性)懒豹。
    消息轉(zhuǎn)發(fā)給父類。
  • setter會調(diào)用父類原來的方法進行賦值驯用,完成后進行回調(diào)通知脸秽。
  • 移除observer的時候isa指回父類,動態(tài)生成的子類并不會銷毀.

更多內(nèi)容持續(xù)更新

?? 喜歡就點個贊吧????

?? 覺得有收獲的蝴乔,可以來一波记餐,收藏+關(guān)注,評論 + 轉(zhuǎn)發(fā)薇正,以免你下次找不到我????

??歡迎大家留言交流片酝,批評指正,互相學習??挖腰,提升自我??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雕沿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猴仑,更是在濱河造成了極大的恐慌审轮,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁脊,死亡現(xiàn)場離奇詭異断国,居然都是意外死亡贤姆,警方通過查閱死者的電腦和手機榆苞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霞捡,“玉大人坐漏,你說我怎么就攤上這事。” “怎么了赊琳?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵街夭,是天一觀的道長。 經(jīng)常有香客問我躏筏,道長板丽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任趁尼,我火速辦了婚禮埃碱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酥泞。我一直安慰自己砚殿,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布芝囤。 她就那樣靜靜地躺著似炎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悯姊。 梳的紋絲不亂的頭發(fā)上羡藐,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音悯许,去河邊找鬼传睹。 笑死,一個胖子當著我的面吹牛岸晦,可吹牛的內(nèi)容都是我干的欧啤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼启上,長吁一口氣:“原來是場噩夢啊……” “哼邢隧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冈在,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤倒慧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后包券,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纫谅,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年溅固,在試婚紗的時候發(fā)現(xiàn)自己被綠了付秕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡侍郭,死狀恐怖询吴,靈堂內(nèi)的尸體忽然破棺而出掠河,到底是詐尸還是另有隱情,我是刑警寧澤猛计,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布唠摹,位于F島的核電站,受9級特大地震影響奉瘤,放射性物質(zhì)發(fā)生泄漏勾拉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一盗温、第九天 我趴在偏房一處隱蔽的房頂上張望望艺。 院中可真熱鬧,春花似錦肌访、人聲如沸找默。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惩激。三九已至,卻和暖如春蟹演,著一層夾襖步出監(jiān)牢的瞬間风钻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工酒请, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骡技,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓羞反,卻偏偏與公主長得像布朦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子昼窗,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

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