問題:
1崔兴、KVO的使用彰导?實(shí)現(xiàn)原理?(為什么要?jiǎng)?chuàng)建子類來實(shí)現(xiàn))
2敲茄、KVC的使用位谋?實(shí)現(xiàn)原理?(KVC拿到key以后堰燎,是如何賦值的掏父?知不知道集合操作符,能不能訪問私有屬性秆剪,能不能直接訪問_ivar)
3赊淑、問setter爵政、getter,比如@property關(guān)鍵字陶缺,如果setter钾挟、getter是在什么時(shí)候調(diào)用都不知道,那更別談kvc了饱岸。
附3篇參考文章
文章1
掺出、如何優(yōu)雅地使用 KVO
寫了一個(gè)Demo簡單介紹了KVO的使用,并引出 Facebook 開源的 KVOController框架
注:
使用FBKVOController有一個(gè)坑苫费,自己監(jiān)聽自己的屬性時(shí)會(huì)形成循環(huán)引用汤锨。如:[self.kvoController observe:self ......] (self->_objectInfosMap->self)。 如果換成 [self.KVOControllerNonRetaining obseve:self ......]不會(huì)循環(huán)引用百框,但是又會(huì)造成在self這個(gè)對象釋放->引發(fā)kvoControlloer釋放時(shí)闲礼,kvoController取內(nèi)部的_objectInfosMap取到的觀察者為nil, 沒法取消kvo觀察,導(dǎo)致崩潰琅翻。
文章2
位仁、Objective-C中的KVC和KVO
玉令天下的博客柑贞,寫的蠻不錯(cuò)的方椎。
文章3
、iOS底層原理總結(jié) - 探尋KVO本質(zhì)
文中多處的實(shí)驗(yàn)驗(yàn)證還是挺不錯(cuò)的钧嘶。
KVO的兩個(gè)方法介紹:
1. 添加監(jiān)聽
通過以下方法添加一個(gè)監(jiān)聽者:
-(void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context;
我們重點(diǎn)關(guān)注一下這個(gè)方法的4個(gè)參數(shù):
observer:
就是要添加的監(jiān)聽者對象棠众,當(dāng)監(jiān)聽的屬性發(fā)生改變時(shí)就會(huì)去通知該對象,
該對象必須實(shí)現(xiàn) -observeValueForKeyPath:ofObject:change:context: 方法有决,
要不然程序會(huì)拋出異常闸拿。
keyPath:
就是要被監(jiān)聽的屬性,這里和KVC的規(guī)則一樣书幕。但是這個(gè)值不能傳nil新荤,要不然會(huì)報(bào)錯(cuò)。
通常我們在用的時(shí)候會(huì)傳一個(gè)與屬性同名的字符串台汇,但是這樣可能會(huì)因?yàn)槠磳戝e(cuò)誤苛骨,導(dǎo)致監(jiān)聽不成功,
一個(gè)推薦的做法是苟呐,用這種方式NSStringFromSelector(@selector(propertyName))痒芝,
其實(shí)就是是將屬性的getter方法轉(zhuǎn)換成了字符串,這樣做的好處就是牵素,如果你寫錯(cuò)了屬性名严衬,
xcode會(huì)用警告提醒你。
options:
是一些配置選項(xiàng)笆呆,用來指明通知發(fā)出的時(shí)機(jī)和通知響應(yīng)方法
-observeValueForKeyPath:ofObject:change:context:
的change字典中包含哪些值请琳,它的取值有4個(gè)粱挡,定義在NSKeyValueObservingOptions中,
可以用|符號連接俄精,如下:
1. NSKeyValueObservingOptionNew:
指明接受通知方法參數(shù)中的change字典中應(yīng)該包含改變后的新值抱怔。
2. NSKeyValueObservingOptionOld:
指明接受通知方法參數(shù)中的change字典中應(yīng)該包含改變前的舊值。
3. NSKeyValueObservingOptionInitial:
當(dāng)指定了這個(gè)選項(xiàng)時(shí)嘀倒,在addObserver:forKeyPath:options:context:消息被發(fā)出去后屈留,
甚至不用等待這個(gè)消息返回,監(jiān)聽者對象會(huì)馬上收到一個(gè)通知测蘑。
這種通知只會(huì)發(fā)送一次灌危,你可以利用這種“一次性“的通知來確定要監(jiān)聽屬性的初始值。
4. NSKeyValueObservingOptionPrior:當(dāng)指定了這個(gè)選項(xiàng)時(shí)碳胳,在被監(jiān)聽的屬性被改變前勇蝙,
監(jiān)聽者對象就會(huì)收到一個(gè)通知(一般的通知發(fā)出時(shí)機(jī)都是在屬性改變后,
雖然change字典中包含了新值和舊值挨约,但是通知還是在屬性改變后才發(fā)出)味混,
這個(gè)通知會(huì)包含一個(gè)NSKeyValueChangeNotificationIsPriorKeykey,
其對應(yīng)的值為一個(gè)NSNumber類型的YES诫惭。
context:
添加監(jiān)聽方法的最后一個(gè)參數(shù)翁锡,是一個(gè)可選的參數(shù),可以傳任何數(shù)據(jù)夕土,
這個(gè)參數(shù)最后會(huì)被傳到監(jiān)聽者的響應(yīng)方法中馆衔,可以用來區(qū)分不同通知,也可以用來傳值怨绣。
如果你要用context來區(qū)分不同的通知角溃,一個(gè)推薦的做法是聲明一個(gè)靜態(tài)變量,其保持它自己的地址篮撑,
這個(gè)變量沒有什么意義减细,但是卻能起到區(qū)分的作用,如下:
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
2. 接受通知
前面說過了赢笨,每一個(gè)監(jiān)聽者對象都必須實(shí)現(xiàn)下面這個(gè)方法來接收通知:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
keyPath未蝌,object,context和監(jiān)聽方法中指定的一樣质欲,
關(guān)于change參數(shù)树埠,它是一個(gè)字典,有五個(gè)常量作為它的鍵:
NSString *const NSKeyValueChangeKindKey;
NSString *const NSKeyValueChangeNewKey;
NSString *const NSKeyValueChangeOldKey;
NSString *const NSKeyValueChangeIndexesKey;
NSString *const NSKeyValueChangeNotificationIsPriorKey;
分析下:
NSKeyValueChangeKindKey:
指明了變更的類型嘶伟,值為“NSKeyValueChange”枚舉中的某一個(gè)怎憋,類型為NSNumber。
enum {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4
};
typedef NSUInteger NSKeyValueChange;
一般情況下返回的都是1也就是第一個(gè)NSKeyValueChangeSetting,
但是如果你監(jiān)聽的屬性是一個(gè)集合對象的話绊袋,當(dāng)這個(gè)集合中的元素被插入毕匀,刪除,替換時(shí)癌别,
就會(huì)分別返回NSKeyValueChangeInsertion皂岔,NSKeyValueChangeRemoval和NSKeyValueChangeReplacement。
NSKeyValueChangeNewKey:
被監(jiān)聽屬性改變后新值的key展姐,當(dāng)監(jiān)聽屬性為一個(gè)集合對象躁垛,
且NSKeyValueChangeKindKey不為NSKeyValueChangeSetting時(shí),
該值返回的是一個(gè)數(shù)組圾笨,包含插入教馆,替換后的新值(刪除操作不會(huì)返回新值)。
NSKeyValueChangeOldKey:
被監(jiān)聽屬性改變前舊值的key擂达,當(dāng)監(jiān)聽屬性為一個(gè)集合對象土铺,
且NSKeyValueChangeKindKey不為NSKeyValueChangeSetting時(shí),
該值返回的是一個(gè)數(shù)組板鬓,包含刪除悲敷,替換前的舊值(插入操作不會(huì)返回舊值)
NSKeyValueChangeIndexesKey:
如果NSKeyValueChangeKindKey的值為NSKeyValueChangeInsertion,
NSKeyValueChangeRemoval, 或者 NSKeyValueChangeReplacement,
這個(gè)鍵的值是一個(gè)NSIndexSet對象俭令,包含了增加后德,移除或者替換對象的index。
NSKeyValueChangeNotificationIsPriorKey:
如果注冊監(jiān)聽者是options中指明了NSKeyValueObservingOptionPrior唤蔗,
change字典中就會(huì)帶有這個(gè)key探遵,值為NSNumber類型的YES.
最后窟赏,完整的change字典大概就類似這樣:
NSDictionary *change = @{
NSKeyValueChangeKindKey : NSKeyValueChange(枚舉值),
NSKeyValueChangeNewKey : newValue,
NSKeyValueChangeOldKey : oldValue,
NSKeyValueChangeIndexesKey : @[NSIndexSet, NSIndexSet],
NSKeyValueChangeNotificationIsPriorKey : @1,
};
3妓柜、當(dāng)觀察者不需要監(jiān)聽時(shí),可以調(diào)用removeObserver:forKeyPath:方法將KVO移除涯穷。
需要注意的是棍掐,調(diào)用removeObserver需要在觀察者消失之前,否則會(huì)導(dǎo)致Crash拷况。
注意點(diǎn)
1作煌、KVO的addObserver和removeObserver需要是成對的,
如果重復(fù)remove則會(huì)導(dǎo)致NSRangeException類型的Crash赚瘦,
保險(xiǎn)的做法是粟誓,把remove操作放在try/catch中。
如果忘記remove則會(huì)在觀察者釋放后再次接收到KVO回調(diào)時(shí)Crash起意。
2鹰服、蘋果官方推薦的方式是,在init的時(shí)候進(jìn)行addObserver,在dealloc時(shí)removeObserver悲酷,
這樣可以保證add和remove是成對出現(xiàn)的套菜,是一種比較理想的使用方式。
4. KVO內(nèi)部實(shí)現(xiàn)原理
1设易、KVO是通過isa-swizzling(黑魔法)技術(shù)實(shí)現(xiàn)的
2逗柴、當(dāng)某個(gè)類的屬性對象第一次被觀察(addObserver)時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類顿肺,
在這個(gè)派生類中重寫基類中任何被觀察屬性的setter 方法戏溺。
派生類在被重寫的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制
3、如果原類為Person屠尊,那么生成的派生類名為NSKVONotifying_Person
4于购、每個(gè)類對象中都有一個(gè)isa指針指向當(dāng)前類,當(dāng)一個(gè)類對象的第一次被觀察知染,
那么系統(tǒng)會(huì)偷偷將isa指針指向動(dòng)態(tài)生成的派生類肋僧,從而在給被監(jiān)控屬性賦值時(shí)執(zhí)行的是派生類的setter方法
5、鍵值觀察通知依賴于NSObject 的兩個(gè)方法:
willChangeValueForKey: 和 didChangevlueForKey:控淡;在一個(gè)被觀察屬性發(fā)生改變之前嫌吠,
willChangeValueForKey:一定會(huì)被調(diào)用,這就會(huì)記錄舊的值掺炭。
而當(dāng)改變發(fā)生后辫诅,didChangeValueForKey:會(huì)被調(diào)用,
繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用涧狮。
6炕矮、KVO的這套實(shí)現(xiàn)機(jī)制中蘋果還偷偷重寫了class方法,讓我們誤認(rèn)為還是使用的當(dāng)前類者冤,
從而達(dá)到隱藏生成的派生類
5. _NSsetIntValueAndNotify
NSKVONotifyin_Person中的setage方法中其實(shí)調(diào)用了 Fundation框架中C語言函數(shù) _NSsetIntValueAndNotify肤视,_NSsetIntValueAndNotify內(nèi)部做的操作相當(dāng)于,首先調(diào)用willChangeValueForKey 將要改變方法涉枫,之后調(diào)用父類的setage方法對成員變量賦值邢滑,最后調(diào)用didChangeValueForKey已經(jīng)改變方法。didChangeValueForKey中會(huì)調(diào)用監(jiān)聽器的監(jiān)聽方法愿汰,最終來到監(jiān)聽者的observeValueForKeyPath方法中困后。
完。