1.kvo簡介
KVO全稱KeyValueObserving算吩,是蘋果提供的一套事件通知機(jī)制闻妓。允許對象監(jiān)聽另一個對象特定屬性的改變返干,并在改變時接收到事件。由于KVO的實(shí)現(xiàn)機(jī)制席揽,所以對屬性才會發(fā)生作用顽馋,一般繼承自NSObject的對象都默認(rèn)支持KVO。
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實(shí)現(xiàn)幌羞。區(qū)別在于寸谜,相對于被觀察者和觀察者之間的關(guān)系,KVO是一對一的属桦,而不一對多的熊痴。KVO對被監(jiān)聽對象無侵入性,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽聂宾。
KVO可以監(jiān)聽單個屬性的變化果善,也可以監(jiān)聽集合對象的變化。通過KVC的mutableArrayValueForKey:等方法獲得代理對象系谐,當(dāng)代理對象的內(nèi)部對象發(fā)生改變時巾陕,會回調(diào)KVO監(jiān)聽的方法。集合對象包含NSArray和NSSet蔚鸥。
看下官方文檔
KVO是一套通知機(jī)制惜论,這臺機(jī)制讓一個對象可以收到一個通知,這個通知描述的是另一個對象它的屬性發(fā)生的變化止喷。
2.kvo使用
運(yùn)行:
這就是kvo的基本使用 官網(wǎng)上也有介紹
Person對象監(jiān)聽Account對象
1馆类、注冊觀察者:addObserver:forKeyPath:options:context:
2、實(shí)現(xiàn)通知回調(diào):observeValueForKeyPath:ofObject:change:context:
3弹谁、移除觀察者:?removeObserver:forKeyPath:
// observer: 觀察誰乾巧,弱引用添加
// keyPath:觀察什么
// options:觀察什么的變化句喜,枚舉值 - 新值、舊值
// context:上下文 為了進(jìn)行觀察這區(qū)分沟于,解決問題咳胃,可以在監(jiān)聽回調(diào)方法中減少判斷,代碼可讀性會降低
context
官方:
addObserver:forKeyPath:options:context: message中的上下文指針包含將在相應(yīng)的更改通知中傳遞回觀察者的任意數(shù)據(jù)旷太。您可以指定NULL并完全依賴于鍵路徑字符串來確定更改通知的來源展懈,但是這種方法可能會給其超類出于不同的原因也在觀察相同鍵路徑的對象造成問題。
一種更安全供璧、更可擴(kuò)展的方法是使用content來確保接收到的通知是發(fā)送給觀察者的存崖,而不是超類。
類中唯一命名的靜態(tài)變量的地址可以作為一個良好的上下文(content)睡毒。在超類或子類中以類似方式選擇的上下文將不太可能重疊来惧。您可以為整個類選擇一個上下文,并依賴通知消息中的關(guān)鍵路徑字符串來確定更改的內(nèi)容演顾」┎螅或者,您可以為觀察到的每個鍵路徑創(chuàng)建不同的上下文钠至,這樣就完全不必進(jìn)行字符串比較葛虐,從而提高通知解析的效率。
大意就是:你和你的父類可能同時監(jiān)聽了相同的key path棕洋;如果沒有上下文context的支持無法做到區(qū)分挡闰。
這樣就做出了區(qū)分
移除觀察者的純粹性和必要性
1乒融、闡述了純粹性:已除的肯定是之前已經(jīng)添加過的觀察者掰盘,如果未添加就會報(bào)NSRangeException異常,即不能重復(fù)添加
2赞季、闡述了必要性:首先觀察者不會自動的remove;被觀察的對象還會向觀察者發(fā)送通知愧捕,如果此時觀察者已經(jīng)released,那么就會出現(xiàn)內(nèi)存訪問異常
3、給出建議使用:在init或viewDidLoad中注冊觀察者addObserver;dealloc中移除觀察者removeObserver
手動和自動
使用automaticallyNotifiesObserversForKey:方法可以實(shí)現(xiàn)對當(dāng)前對象的某個屬性的自動觀察做開關(guān)處理申钩。
設(shè)置為no時就不會響應(yīng) 也就是通過automaticallyNotifiesObserversForKey類方法屏蔽某個屬性的自動通知邏輯
然后手動實(shí)現(xiàn)下面方法就可以正常響應(yīng)
路徑處理
比如我們監(jiān)聽下載的進(jìn)度時次绘,可知下載的進(jìn)度 = 已下載量 / 總下載量,可我們又不想監(jiān)聽兩個量撒遣,這個時候可以使用keyPathsForValuesAffectingValueForKey方法進(jìn)行觀察處理邮偎。
運(yùn)行后結(jié)果:
數(shù)組觀察
輸出發(fā)現(xiàn)沒有變化 但是數(shù)組的確增加了
官方文檔:
根據(jù)官方文檔做如下處理,將修改數(shù)組的位置修改為
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
? ? NSKeyValueChangeSetting = 1, // 設(shè)置
? ? NSKeyValueChangeInsertion = 2, // 插入
? ? NSKeyValueChangeRemoval = 3, // 移除
? ? NSKeyValueChangeReplacement = 4, // 替換
};
上方的2對應(yīng)數(shù)組的addObject等操作义黎,3對應(yīng)數(shù)組的removeObject等操作禾进,4對應(yīng)數(shù)組的replaceObjectAtIndex:withObject:的操作
KVO底層原理探索
KVO觀察屬性
** 通過打印結(jié)果可知,KVO只對屬性進(jìn)行監(jiān)聽廉涕,對成員變量不監(jiān)聽. ** 屬性與成員變量的區(qū)別在于屬性存在 setter方法泻云,而成員變量沒有setter
中間類
self.person的isa的指向發(fā)生了變化
通過斷點(diǎn)打印可知艇拍,在添加監(jiān)聽之后,self.person的isa重新指向了NSKVONotifying_LGPerson類宠纯,對比之前的類多了NSKVONotifying_前綴卸夕。
** 派生類 **:即某個類的子類
自定義方法,查看LGPerson的子類情況婆瓜,代碼如下:
并在self.person添加監(jiān)聽的前后進(jìn)行調(diào)用該方法快集,
查看打印結(jié)果。
其中LGStudent是LGPerson的一個子類廉白,在添加監(jiān)聽方法后發(fā)現(xiàn)LGPerson多了一個子類NSKVONotifying_LGPerson碍讨,說明添加監(jiān)聽方法后,self.person的isa指向了LGPerson的子類蒙秒。
中間類中有什么
是否存在什么方法勃黍。
自定義代碼打印類的方法。
調(diào)用方法
打印
從打印結(jié)果可知晕讲,在NSKVONotifying_LGPerson類中添加了四個方法覆获,分別為:setNickName、class瓢省、dealloc弄息、_isKVOA這四個方法。
_isKVOA?判斷當(dāng)前是否為KVO類
dealloc?釋放
setNickName?需要查看是繼承還是重寫的勤婚,答案是重寫的方法
class
那我們繼續(xù)打印下另一個子類student的方法
子類student先不實(shí)現(xiàn)任何方法?
打印沒有
接下來實(shí)現(xiàn)一個setNickName
打印就有了setNickName
這個時候發(fā)現(xiàn)LGStudent中存在setNickName方法摹量,由此可知NSKVONotifying_LGPerson中的setNickName為重寫的方法
isa是否會還原
NSKVONotifying_LGPerson類是否會移除,self.person的isa是否會指回來馒胆。
在ViewController中的dealloc方法中添加斷點(diǎn)缨称,查看移除之后的self.personisa的指向。
通過打印可知祝迂,在移除監(jiān)聽后睦尽,self.person的isa會重新指向LGPerson
在pop的ViewController也做LGPerson的子類的打印,
結(jié)果如下:
發(fā)現(xiàn)當(dāng)監(jiān)聽被移除后型雳,NSKVONotifying_LGPerson類并沒有被移除当凡,而是仍然存在。
結(jié)論是 對象的isa會重新指向 動態(tài)生成的類會一直存在
重寫的setter方法內(nèi)做了什么處理
猜測一下纠俭,首先沿量,在調(diào)用NSKVONotifying_LGPerson重寫setter方法的時候,改變的是其父類LGPerson的nickName的值冤荆,那么在重寫的setter方法中一定有對父類nickName進(jìn)行傳值的操作朴则。
設(shè)置觀察self->_pserson->_nickName,具體命令為:
watchpoint set variable self->_person->_nickName
執(zhí)行命令后的結(jié)果匙赞。進(jìn)入斷點(diǎn)佛掖。
查看左側(cè)堆棧妖碉,可知2、3芥被、4步是隱藏邏輯為匯編語言
堆棧編號1為LGPerson的setter方法
堆棧編號2在斷點(diǎn)前后分別執(zhí)行NSKeyValueWillChange方法以及NSKeyValueDidChange方法欧宜。
結(jié)論:在willChange與didChange之間調(diào)用父類的賦值方法,因此拴魄,父類的值得以改變冗茸。