iOS KVO、KVC的使用和探究

一晌梨、KVO

KVO 的作用:

kvo 就是監(jiān)聽某個對象的屬性桥嗤,在該屬性的值發(fā)生變化時,通知觀察者仔蝌。

KVO 的簡單實用

- (void)viewDidLoad {
    [super viewDidLoad]
    self.testKvo = [[TestKVO alloc]init];
    self.testKvo2 = [[TestKVO alloc]init];
    self.testKvo.name = @"testKvo初始值";
    self.testKvo2.name = @"testKvo2初始值";
    [self.testKvo addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"123"];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.testKvo.name = @"testKvo改變值";
    self.testKvo2.name = @"testKvo2改變值";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
    NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
    NSLog(@"object = %@   testKvo = %@   testKvo2 =%@",object,self.testKvo,self.testKvo2);
    NSLog(@"oldvalue = %@",oldValue);
    NSLog(@"newvalue = %@",newValue);
    NSLog(@"context = %@",context);
}

我們運行泛领,可以看到結果:


kvo示例.png

參數說明:

  • addObserver:監(jiān)聽器,誰來監(jiān)聽
  • forKeyPath:要監(jiān)聽的屬性
  • options:一般是 NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew敛惊,新值和舊值
  • context:添加監(jiān)聽時渊鞋,傳入的時候,在收到監(jiān)聽時瞧挤,可以獲得锡宋,從上面的代碼和log日志,我們也可以看到

分析:

  1. 對象調了 setter 方法特恬,監(jiān)聽器就會收到監(jiān)聽的回調
  2. 上面的代碼中执俩,兩個同類型的對象,在都調了setter 方法時癌刽,只有 testKvo 收到了回調役首,而 testKvo2 并沒有收到回調
    由此,我們可以看到显拜,同一個類型的對象在都調了setter 方法時衡奥,只有添加了監(jiān)聽的對象才會收到監(jiān)聽的回調。

探究過程

  • 我們知道對象方法存放在類對象中远荠,既然都是調的是 setter 方法矮固,為什么testKvo2 就沒有收到監(jiān)聽呢,我們可以猜想到譬淳,這兩個對象的 setter 是不是不一樣档址,添加監(jiān)聽了的對象的setter 方法被系統(tǒng)內部改變了呢盹兢?
  • 那我們用runtime 來打印一下這兩個對象的類對象是不是有什么變化。注意:使用 Class 方法獲取的對象類型不是準確的辰晕,我們也可以猜到蘋果這么做是不想給我們暴露這個類,想要獲取類的真實類型使用runtime 的 object_getClass()函數
    NSLog(@"添加監(jiān)聽前 testKvo = %@  testKvo2= %@",object_getClass(self.testKvo),object_getClass(self.testKvo2));
    [self.testKvo addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"123"];
    NSLog(@"添加監(jiān)聽后 testKvo = %@  testKvo2= %@",object_getClass(self.testKvo),object_getClass(self.testKvo2));
  • 查看類對象.png
  • 我們可以看到确虱,在添加了監(jiān)聽后的對象含友,類型發(fā)生了改變,變成了NSKVONotifying_TestKVO,而沒有添加監(jiān)聽類型并沒有改變校辩。
  • 由此窘问,我們可以看出kvo的實現原理是:創(chuàng)建了一個前綴為NSKVONotifying_ 新的類,并且這個類繼承監(jiān)聽對象原來的類宜咒,并且重寫了setter 方法惠赫,在對象調了setter 方法時,通知監(jiān)聽器故黑,實現回調儿咱。
    -為什么我們說是重寫了 setter 方法,我們使用 runtime 的函數:methodForSelector 來看下到底是不是
    NSLog(@"添加監(jiān)聽前 testKvo = %p  testKvo2= %p",[self.testKvo methodForSelector:@selector(setName:)],[self.testKvo2 methodForSelector:@selector(setName:)]);
    [self.testKvo addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:@"123"];
    NSLog(@"添加監(jiān)聽后 testKvo = %p  testKvo2= %p",[self.testKvo methodForSelector:@selector(setName:)],[self.testKvo2 methodForSelector:@selector(setName:)]);
  • 查看setter方法.png

我們看到场晶,在 添加監(jiān)聽前 兩個對象的 setter 方法是同一個混埠,而在添加監(jiān)聽后的對象的setter 方法地址發(fā)生了改變。而且我們在調試環(huán)境下還可以打印出setter 方法的相關信息诗轻。添加監(jiān)聽前是setName钳宪,而添加監(jiān)聽后方法是 _NSSetObjectValueAndNotify ,而且是屬于 Foundation 框架下的方法扳炬。由此吏颖,我們可以知道確實是重寫了setter 方法。

kVO 實現原理總結:

  • 動態(tài)生成一個子類恨樟,讓這個對象的isa 指針指向一個新的類(這個新的類集成自原來的類)
  • 當修改對象的屬性時 會調用重寫的setter 方法半醉。這個 setter 方法內部是 先調willChangeValueForKey, 再調父類原來的setter方法劝术,再調didChangeValueForKey奉呛,最后觸發(fā)監(jiān)聽的回調方法

補充:

  • 直接修改成員變量是不會觸發(fā) kvo 監(jiān)聽的
  • 使用 kvc 是會觸發(fā) kvo 監(jiān)聽的(原因是,使用kvc ,也會來到setter 方法夯尽。kvc 內部調用了 willChangeValueForKey 和 didChangeValueForKey 方法)

二瞧壮、 KVC

基本使用

  • setValue:forKey 和 setValue:forKeyPath兩個方法
  • setValue forkey 和 setValue forKeyPath 的區(qū)別在于,forKeyPath 是可以多深層次訪問的匙握。例如:有兩個類 Psrson 和Student咆槽,Psrson 類里面有個Student 類型的對象,Student 類里面有個 score 屬性圈纺。那么就可以這么使用:[person setValue:@80 forKeyPath:@"student.score" ]

setValue 設值順序

查找setKey 方法秦忿,有則調用麦射,沒有則查找看有沒有_setKey方法,有則調用灯谣。沒有則查看 accessInstanceVariablesDirectly 這個類方法的返回值潜秋,是yes則按照_key 、_isKey 胎许、key 峻呛、iskey 順序查找成員變量。如果沒有找到則拋出異常 NSUnknownKeyException 辜窑。如果accessInstanceVariablesDirectly返回值是 NO钩述,則程序拋出異常

測試setValue 設值順序

  • 新建一個名為 TestKVC的類,并且沒有給這個類添加為name的屬性穆碎,在這個類里面添加setName 牙勘、_setName方法和 _name、isName所禀、name 方面、isName四個成員變量
    TestKVC *kvc = [[TestKVC alloc]init];
    [kvc setValue:@"測試名字" forKey:@"name"];

- (void)setName:(NSString *)name
{
    NSLog(@"%s %@",__func__,name);
}

- (void)_setName:(NSString *)name
{
    NSLog(@"%s %@",__func__,name);
}
  • 我們可以看到結果如下:
    同時有setName和_setName方法.png
  • 然后我們 注釋setName 方法 看結果:
    注釋了setName方法.png

    由此可見,kvc 確實是按照 setKey 和_setKey 方法查找的色徘。
  • 接下來葡幸,我們注釋掉setName 和_setName 方法,在里面實現 accessInstanceVariablesDirectly 類方法再來看看結果
+ (BOOL)accessInstanceVariablesDirectly
{
    return NO;
}
  • 返回結果為NO時.png
  • 這個時候贺氓,程序會直接拋出異常蔚叨,我們將返回結果改成 YES繼續(xù)看看結果
  • 查看給哪個成員變量賦值.png

    我們可以看到是先給_name 賦值的,然后我們** 依次注釋 _name 辙培、 isName 蔑水、name 、isName 查看結果

  • 注釋_name.png
  • 注釋_isName.png
  • 注釋name.png
  • 注釋了isName.png

    我們可以看到扬蕊,kvc 確實是按 _name 搀别、 isName 、name 尾抑、isName 查找的歇父。

valueForKey 的取值順序

  • 先按照 getKey 、key 再愈、isKey 榜苫、_key 順序查找方法取值,如果沒有找到方法則查看accessInstanceVariablesDirectly 類方法的返回值翎冲,如果返回的是 NO垂睬,則拋出異常。如果返回的是YES,按照_key 驹饺、_isKey 钳枕、key 、isKey 順序查找成員變量赏壹,如果沒有找到鱼炒,則拋出異常。

測試過程

參照上面valueForkey 取值的方式蝌借,可以驗證是否正確昔瞧。這里就不贅述了。

這些就是在最近學習探究實現原理的全部內容和過程骨望,有問題或者疑問的歡迎提出硬爆,共同進步欣舵,謝謝 ~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末擎鸠,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子缘圈,更是在濱河造成了極大的恐慌劣光,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糟把,死亡現場離奇詭異绢涡,居然都是意外死亡,警方通過查閱死者的電腦和手機遣疯,發(fā)現死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門雄可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缠犀,你說我怎么就攤上這事数苫。” “怎么了辨液?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵虐急,是天一觀的道長。 經常有香客問我滔迈,道長止吁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任燎悍,我火速辦了婚禮敬惦,結果婚禮上,老公的妹妹穿的比我還像新娘谈山。我一直安慰自己仁热,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抗蠢,像睡著了一般举哟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迅矛,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天妨猩,我揣著相機與錄音,去河邊找鬼秽褒。 笑死壶硅,一個胖子當著我的面吹牛,可吹牛的內容都是我干的销斟。 我是一名探鬼主播庐椒,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚂踊!你這毒婦竟也來了约谈?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤犁钟,失蹤者是張志新(化名)和其女友劉穎棱诱,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體涝动,經...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡迈勋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了醋粟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靡菇。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖米愿,靈堂內的尸體忽然破棺而出厦凤,到底是詐尸還是另有隱情,我是刑警寧澤吗货,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布泳唠,位于F島的核電站,受9級特大地震影響宙搬,放射性物質發(fā)生泄漏笨腥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一勇垛、第九天 我趴在偏房一處隱蔽的房頂上張望脖母。 院中可真熱鬧,春花似錦闲孤、人聲如沸谆级。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肥照。三九已至脚仔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舆绎,已是汗流浹背鲤脏。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吕朵,地道東北人猎醇。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像努溃,于是被迫代替她去往敵國和親硫嘶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內容

  • 一梧税、 kvc 1. KVC(Key-value coding)鍵值編碼 通過對象的屬性名(不管該屬性是否暴露)直接...
    不會武功的陳真閱讀 239評論 0 0
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼沦疾,就是指iOS的開發(fā)中,可以允許開發(fā)者通過K...
    戀空K閱讀 721評論 0 2
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼贡蓖,就是指iOS的開發(fā)中曹鸠,可以允許開發(fā)者通過K...
    暮年古稀ZC閱讀 2,137評論 2 9
  • KVC定義 KVC(Key-value coding)鍵值編碼煌茬,就是指iOS的開發(fā)中斥铺,可以允許開發(fā)者通過Key名直...
    SheIsMySin_72e7閱讀 377評論 0 0
  • image.png KVC KVC定義 KVC(Key-value coding)鍵值編碼,就是指iOS的開發(fā)中坛善,...
    草根小強閱讀 278評論 0 0