KVO的全稱是Key-Value Observing谦炬,俗稱“鍵值監(jiān)聽”昔榴,可以用于監(jiān)聽某個對象屬性值的改變逞怨。
對于iOS開發(fā)者而言,相信大家都使用過KVO耍鬓。但是對它的原理可能就不太清楚了阔籽。KVO是Runtime運行時機制的又一重大的應用,下面就對KVO的原理做一個詳盡的闡述牲蜀。
創(chuàng)建一個新的項目笆制,然后在項目中創(chuàng)建一個ZPPerson類,這個類中有一個age屬性涣达。
然后在ViewController.m文件中創(chuàng)建一個person對象在辆,并給它添加KVO監(jiān)聽。
當用戶點擊屏幕的時候峭判,改變person對象的age屬性的值。
這個時候系統(tǒng)就會調(diào)用監(jiān)聽方法了棕叫。
下面闡述這個過程中KVO的實現(xiàn)原理:
- 在給這個person實例對象添加KVO監(jiān)聽之后林螃,系統(tǒng)會利用Runtime機制動態(tài)地創(chuàng)建一個ZPPerson類的子類,名字就叫做"NSKVONotifying_ZPPerson"俺泣。然后系統(tǒng)會把這個person實例對象里面的isa指針由原來的指向ZPPerson類的class對象變?yōu)楝F(xiàn)在的指向NSKVONotifying_ZPPerson類的class對象疗认;
- 新創(chuàng)建的子類"NSKVONotifying_ZPPerson"的class對象里面存儲著isa指針完残、superclass指針、"setAge:"實例方法横漏、"class"實例方法谨设、"dealloc"實例方法以及"_isKVOA"實例方法等。其中系統(tǒng)會重寫"setAge:"實例方法缎浇,重寫后的該方法與它父類中的該方法的實現(xiàn)是不一樣的扎拣,只不過方法的名稱是一樣的而已;
- 添加完KVO監(jiān)聽之后素跺,當開發(fā)者調(diào)用"setAge:"實例方法來修改person對象的age屬性的時候,根據(jù)上面所述,這個instance對象里面的isa指針已經(jīng)由原來的指向ZPPerson類的class對象變?yōu)榱爽F(xiàn)在的指向NSKVONotifying_ZPPerson類的class對象了瓶摆,所以系統(tǒng)根據(jù)這個instance對象里面的isa指針找到的是NSKVONotifying_ZPPerson類的class對象弟头,然后在這個class對象里面找到重寫后的"setAge:"實例方法,最后再進行調(diào)用踩验。這個重寫的"setAge:"實例方法里面會調(diào)用Foundation框架里面的C語言函數(shù)"_NSSetIntValueAndNotify();"鸥诽,這個函數(shù)的實現(xiàn)里面首先會執(zhí)行"[self willChangeValueForKey:@"age"];"代碼,然后再執(zhí)行"[super setAge:age];"代碼箕憾,在執(zhí)行這句代碼的時候就會執(zhí)行它的父類牡借,也就是ZPPerson類里面的"setAge:"方法,從而真正更改這個屬性的值厕九,最后再執(zhí)行"[self didChangeValueForKey:@"age"];"代碼蓖捶。"didChangeValueForKey:"這個方法里面會通知監(jiān)聽器"age"屬性的值被改變了,即調(diào)用"- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context"監(jiān)聽方法扁远,從而實現(xiàn)了對對象的某個屬性進行監(jiān)聽的目的俊鱼。
可以從下面的圖中看出NSKVONotifying_ZPPerson類和ZPPerson類的關(guān)系:
除了上述的改變對象的屬性值的時候會自動調(diào)用KVO的監(jiān)聽方法之外,想要在不改變對象屬性值的時候也能自動調(diào)用KVO的監(jiān)聽方法畅买,應該怎么做呢并闲?
手動觸發(fā)KVO:
在點擊屏幕的時候調(diào)用手動觸發(fā)KVO的方法:
在方法中需要主動調(diào)用"willChangeValueForKey:"和 "didChangeValueForKey:"方法。
這樣的話谷羞,在點擊屏幕的時候就會手動觸發(fā)KVO的監(jiān)聽方法了"- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context"帝火。
以上就是對KVO原理的闡述。
補充:
修改對象里面的成員變量的值是不能夠觸發(fā)KVO的監(jiān)聽方法的湃缎,代碼如下所示:
在ZPPerson類里添加一個_age成員變量:
在ViewController.m文件中給age屬性賦值:
點擊屏幕的時候修改_age成員變量的值:
運行之后可以知道通過這種方式改變對象的成員變量的值是無法自動觸發(fā)KVO的監(jiān)聽方法的"- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context"犀填。
修改對象的成員變量無法觸發(fā)KVO監(jiān)聽方法的原因:
原因就在于對于已經(jīng)被添加KVO監(jiān)聽的對象而言,instance對象的isa指針已經(jīng)被改變?yōu)榱酥赶蛳到y(tǒng)新創(chuàng)建的子類的class對象了嗓违,然后在這個子類的class對象里面找到了(setAge:)實例方法九巡,最后再進行調(diào)用。由之前的Demo可知蹂季,當調(diào)用新創(chuàng)建的子類的class對象中的屬性的set實例方法的時候其實是在調(diào)用Foundation框架里面的C語言函數(shù)"_NSSetIntValueAndNotify"冕广,這個函數(shù)中會執(zhí)行"didChangeValueForKey:"方法疏日,此方法會觸發(fā)KVO的監(jiān)聽方法。由此可知KVO的本質(zhì)是調(diào)用set實例方法撒汉,即只有通過調(diào)用屬性的set實例方法來修改屬性的值才能觸發(fā)KVO的監(jiān)聽方法沟优,而修改對象的成員變量就沒有調(diào)用屬性的set方法,所以是不能夠觸發(fā)KVO的監(jiān)聽方法的睬辐。
如果想要在修改對象的成員變量的值的時候成功觸發(fā)KVO的監(jiān)聽方法的話就要手動進行觸發(fā):
在手動觸發(fā)方法的實現(xiàn)里面要調(diào)用"willChangeValueForKey:"和 "didChangeValueForKey:"方法:
運行之后就可以在控制臺上看到成功調(diào)用了KVO的監(jiān)聽方法了:
”三人行挠阁,必有我?guī)熝伞埃?歡迎各位批評指正。
如果您還覺得我寫的不錯的話請您點贊加關(guān)注溉委,您的肯定是我前進的最大動力鹃唯!
我是愛學習也愛您的樹懶O(∩_∩)O