在談?wù)揔VO和KVC之前望浩,先引出幾個(gè)問題
問題一:iOS用什么方式實(shí)現(xiàn)KVO瀑晒,你可以理解成KVO本質(zhì)是什么?
1.利用Runtime API動(dòng)態(tài)生成一個(gè)子類,并且讓instance對(duì)象的isa指向這個(gè)全新的子類
2.當(dāng)修改instance對(duì)象的屬性時(shí)赚瘦,會(huì)調(diào)用Foundation的_NSSetXXXValueAndNotify 函數(shù),XXX代表更改的類型
3.重寫class方法
4.在willChangeValueForKey:和didChangeValueForKey:之間調(diào)用父類原來的seter方法奏寨,并且內(nèi)部會(huì)觸發(fā)監(jiān)聽器的Observer的監(jiān)聽方法(observeValueForKeyPath:ofObject:change:context:)
添加兩張圖片:
問題二:如何手動(dòng)觸發(fā)KVO起意?
手動(dòng)調(diào)用willChangeValueForKey和didChangeValueForKey:
問題三:通過指針->修改成員變量會(huì)觸發(fā)KVO么?
不會(huì)觸發(fā)KVO病瞳,因?yàn)闆]有調(diào)用到setter方法
問題四:什么是KVC揽咕?修改KVC的屬性會(huì)觸發(fā)KVO么?
Key-Value Coding套菜,即鍵值編碼心褐。它是一種不通過存取方法,而通過屬性名稱字符串間接訪問屬性的機(jī)制
會(huì)觸發(fā)KVO
問題五:KVC的賦值和取值過程是怎樣的笼踩?或者KVC的實(shí)現(xiàn)原理是什么逗爹?
賦值:
當(dāng)調(diào)用setValue:屬性值 forKey:@"name"的代碼時(shí),底層的執(zhí)行機(jī)制如下:
1.程序優(yōu)先調(diào)用set<Key>:屬性值方法嚎于,代碼通過setter方法完成設(shè)置掘而,注意,這里的<key>是指成員變量名于购,首字母大小要符合KVC的命名規(guī)則袍睡,下面也是這樣的原則
2.如果沒有找到setName:方法,KVC機(jī)制會(huì)檢查一個(gè)類方法+(BOOL)accessInstanceVariableDirectory方法沒有返回YES肋僧,默認(rèn)該方法會(huì)返回YES斑胜,如果你重寫了該方法讓其返回NO的話,那么在這一步KVC會(huì)執(zhí)行setValue:forUndefinedKey:方法嫌吠,不過一般不會(huì)這樣做止潘,因?yàn)檫@樣做相當(dāng)于打破了KVC的規(guī)則,如果返回YES的話辫诅,KVC機(jī)制會(huì)搜索該類里面有沒有名為_<key>的成員變量凭戴,無論該變量是在類接口處定義,還是在類實(shí)現(xiàn)中定義炕矮,也無論用了什么樣的訪問修飾符狮杨,只要存在以_<key>命名的變量袭蝗,KVC都可以對(duì)該成員變量賦值
3.如果該類沒有set<key>:方法日矫,也沒有_<key>成員變量字柠,KVC機(jī)制會(huì)搜索_is<Key>的成員變量
4.如果沒有set<Key>:方法,也沒有_<key>和_is<Key>成員變量邢滑,KVC機(jī)制再會(huì)繼續(xù)搜索<key>和is<Key>的成員變量腐螟,再給他們賦值
5.如果上面列出的方法或者成員變量都不存在,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的setValue:forUndefinedKey:方法,默認(rèn)是拋出異常
如果開發(fā)者想讓這個(gè)類禁用KVC里遭垛,那么重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可尼桶,這樣的話如果KVC沒有找到set<Key>:屬性名時(shí)操灿,會(huì)直接用setValue:forUndefinedKey:方法
取值:
當(dāng)調(diào)用valueForKey:@”name“的代碼時(shí)锯仪,KVC對(duì)key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下:
1.首先按get<Key>,<key>,is<Key>的順序方法查找getter方法趾盐,找到的話會(huì)直接調(diào)用庶喜。如果是BOOL或者Int等值類型, 會(huì)將其包裝成一個(gè)NSNumber對(duì)象救鲤。
2.如果上面的getter沒有找到久窟,KVC則會(huì)查找countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外兩個(gè)方法中的一個(gè)被找到本缠,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所有方法的代理集合(它是NSKeyValueArray斥扛,是NSArray的子類),調(diào)用這個(gè)代理集合的方法丹锹,或者說給這個(gè)代理集合發(fā)送屬于NSArray的方法稀颁,就會(huì)以countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes這幾個(gè)方法組合的形式調(diào)用。還有一個(gè)可選的get<Key>:range:方法楣黍。所以你想重新定義KVC的一些功能匾灶,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法租漂,包括方法簽名阶女。
3.如果上面的方法沒有找到,那么會(huì)同時(shí)查找countOf<Key>哩治,enumeratorOf<Key>,memberOf<Key>格式的方法秃踩。如果這三個(gè)方法都找到,那么就返回一個(gè)可以響應(yīng)NSSet所的方法的代理集合业筏,和上面一樣吞瞪,給這個(gè)代理集合發(fā)NSSet的消息,就會(huì)以countOf<Key>驾孔,enumeratorOf<Key>,memberOf<Key>組合的形式調(diào)用芍秆。
4.如果還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為)翠勉,那么和先前的設(shè)值一樣妖啥,會(huì)按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名,這里不推薦這么做对碌,因?yàn)檫@樣直接訪問實(shí)例變量破壞了封裝性荆虱,使代碼更脆弱。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話,那么會(huì)直接調(diào)用valueForUndefinedKey:
5.還沒有找到的話怀读,調(diào)用valueForUndefinedKey:
問題六:KVC更改屬性或者成員變量會(huì)觸發(fā)KVO么诉位?
答案是前者會(huì),后者不會(huì)
接下來我會(huì)一一通過實(shí)例證明以上問題菜枷?
Key-Value Obersver苍糠,即鍵值觀察。它是觀察者模式的一種衍生啤誊≡啦t;舅枷胧牵瑢?duì)目標(biāo)對(duì)象的某屬性添加觀察蚊锹,當(dāng)該屬性發(fā)生變化時(shí)瞳筏,會(huì)自動(dòng)的通知觀察者。這里所謂的通知是觸發(fā)觀察者對(duì)象實(shí)現(xiàn)的KVO的接口方法牡昆。
** KVO是解決model和view同步的好法子姚炕。**
另外,KVO的優(yōu)點(diǎn)是當(dāng)被觀察的屬性值改變時(shí)是會(huì)自動(dòng)發(fā)送通知的丢烘,這比通知中心需要post通知來說柱宦,簡(jiǎn)單了許多
KVO 使用方法
1.首先給目標(biāo)對(duì)象的屬性添加觀察:
[selfaddObserver:selfforKeyPath:@"xxx"options:(NSKeyValueObservingOptionNew)context:nil];
2.實(shí)現(xiàn)下面方法來接收通知,需要注意各個(gè)參數(shù)的含義:
- (void)addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid*)context;
3.最后要移除觀察者:
- (void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath;
證明1:(問題一:iOS用什么方式實(shí)現(xiàn)KVO铅协,你可以理解成KVO本質(zhì)是什么捷沸?)
利用Runtime API動(dòng)態(tài)生成一個(gè)子類,并且讓instance對(duì)象的isa指向這個(gè)全新的子類?
.當(dāng)修改instance對(duì)象的屬性時(shí)狐史,會(huì)調(diào)用Foundation的_NSSetXXXValueAndNotify 函數(shù)痒给,XXX代表更改的類型
從上面可以看到監(jiān)聽之后,的確是調(diào)用了NSSetIntValueAndNotify,里面的int代表是監(jiān)聽屬性的類型骏全,另外這一點(diǎn)也可以從你逆向中得到苍柏,從你手機(jī)的目錄中/System/Library/Caches/com.apple.dyld獲取動(dòng)態(tài)庫dyld_shared_cache_arm64,然后導(dǎo)出Foundation框架姜贡,具體可以參考這篇文章,然后利用逆向工具Hop來分析试吁,可以看到里面包含這個(gè)方法
重寫class方法
從源碼中可以看到object_getClass中就是調(diào)用class,既然這樣楼咳,為什么我直接打印class會(huì)和object_getClass不一樣呢熄捍,很明顯被監(jiān)聽的類重寫了class
在willChangeValueForKey:和didChangeValueForKey:之間調(diào)用父類原來的seter方法,并且內(nèi)部會(huì)觸發(fā)監(jiān)聽器的Observer的監(jiān)聽方法(observeValueForKeyPath:ofObject:change:context:)
另外想補(bǔ)充一下的就是母怜,這個(gè)被監(jiān)聽的類里面含有的方法:
證明2(如何手動(dòng)觸發(fā)KVO余耽?)
可以看到如果不直接賦值的話,手動(dòng)調(diào)用這兩個(gè)方法也是可以觸發(fā)KVO
證明3:(問題三:通過指針->修改成員變量會(huì)觸發(fā)KVO么苹熏?)
證明4:(修改KVC的屬性會(huì)觸發(fā)KVO么碟贾?)
從上面可以看出更改成員變量并沒有出發(fā)KVO
證明5:(KVC的實(shí)現(xiàn)原理)
用一個(gè)圖說明上述的賦值過程:
用一張圖給說明尋找過程:
證明6:(問題六:KVC更改屬性或者成員變量會(huì)觸發(fā)KVO么恶座?)
從打印結(jié)果可以看出搀暑,修改成員變量并不會(huì)觸發(fā)KVO
其他補(bǔ)充
查看_NSSet*AndNotify的存在
_NSSet*ValueAndNotify 的內(nèi)部實(shí)現(xiàn)
_NSSet*ValueAndNotify本質(zhì)調(diào)用就是先調(diào)用willChangeValueForKey:然后調(diào)用原來的setter方法的實(shí)現(xiàn)沥阳,之后再調(diào)用didChangeValueForKey:這個(gè)方法內(nèi)部會(huì)調(diào)用observer的observeValueForKeyPath:ofObject:change:context:方法
KVC常見的API有:
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
?- (void)setValue:(id)value forKey:(NSString *)key;
?- (id)valueForKeyPath:(NSString *)keyPath;?
- (id)valueForKey:(NSString *)key;
參考鏈接
可以添加微信一起交流學(xué)習(xí):fslskz