KVO與Aspects共存研究

研究版本官方最新版本Release 1.4.2git

現象

  • 在對Obj進行先KVO再Hook其setter函數後,調用obj的setter函數奔潰。
  • 反之,若先Hook再KVO,則正常運行。

緣由

1.先對比兩種添加順序,致使的objisa函數列表的不一樣。github

經過下面的函數打印出,當前的obj→isa 及其函數列表安全

NSLog(@"\nclass - %@\nclass methods - %@", object_getClass(self.testObj), [self allMethodsWithClass:object_getClass(self.testObj)]);
NSLog(@"\nclass - %@\nclass methods - %@", self.testObj.class, [self allMethodsWithClass:self.testObj.class]);
複製代碼

打印結果去除時間戳信息,顯示以下bash

  • 先KVO再進行Hook 函數

  • 先hook再進行KVO 優化

對比能夠發現,若是先KVO再hook ,KVO會先動態生成一個NSKVONotifying_ 前綴的子類,而且會重寫setName: ,添加class 等函數,將class 的返回結果指向子類。 Aspect在hook的時候,若是發現obj.classobject_getClass(obj) 不相等的話(正常發生在對象被KVO重寫過isa),則不會動態地再創建一個子類,而直接會原地hook,添加aspects__setName:forwardInvocation:,交換aspects__setName:setName: 的函數實現。(此時交換的是已經被KVO過的NSKVONotifying_AKObjectsetName:函數實現)spa

此時對name 進行賦值的時候,對obj 調用setName: ,在Aspect中若是不是使用指定AspectPositionInstead的option替換原方法實現,則會走原方法實現,則將對obj調用交換後的方法名aspects__setName:(對應原方法實現),NSKVONotifying_AKObject在處理aspects__setName:時候會調用其子類的方法,可是並不會保存子類原來的setter的方法名,而是直接講本身當前的selector(_cmd)當作方法名,並對子類AKObject進行調用,可是子類中並無實現aspects__setName:,因此產生崩潰。3d

對於先hook再進行KVO的狀況,setName:原類及其兩個生成的子類都有對應的方法實現,因此能夠正常響應。code

解決KVO及Aspects共存的PR

PR地址:github.com/steipete/As…cdn

git地址:github.com/doggy/Aspec…

解決共用的途徑

這個PR創造性的在調用原函數實現那一步驟上作了優化,要調用原函數實現時,先交換回原始函數名及原始的函數實現,再對obj調用原函數名,此時在NSKVONotifying_AKObjectsetName:中拿到的_cmd就是原始的selector,子類中也有對應的方法實現,一切都能正常運行。而後再把invocation的selector對應子類中的方法實現交換回去,完成對原始函數實現的調用。

存在的其餘問題

1.先hook再KVO,若是在清除KVO前對AspectToken進行remove,崩潰。

id token = [object aspect_hookSelector...];// add hook
[object addObserver...]; // add KVO
[token remove]; // crash
複製代碼

緣由:在先hook再KVO這過程當中,obj.isa經歷的變化爲

NSObjectNSObject_Aspects_NSKVONotifying_NSObject_Aspects_

假如在移除KVO前移除Aspects的hook,Aspects須要把當前子類還原爲原始子類,

Aspects的處理邏輯是去除_Aspects_後綴,並將isa指向去除後綴後的類,

isa的變化過程爲

NSKVONotifying_NSObject_Aspects_NSKVONotifying_NSObject

可是,此時程序中並無存在NSKVONotifying_NSObject這個類,所以程序會崩潰。(這個坑在最新版的Aspects中仍然存在,且暫時無解決方法)

2.若是是先KVO再Hook的狀況,要注意在objdealloc時才能remove全部觀察者,若是提早remove掉全部觀察者,hook是對NSKVONotifying_NSObject的方法hook,但移除全部觀察者後就會把對應的NSKVONotifying_NSObject類銷燬,此時isa

NSKVONotifying_NSObjectNSObject

原有的hook將會失效

總結

那麼,咱們應該如何正確且安全地使用同時使用Aspects和KVO呢

  1. 在對同一個對象的某一個實例變量使用Aspects及KVO時,儘可能不要Remove對應的AspectToken,確保不會發生移除後的子類不存在的狀況。
  2. 注意先KVO再Hook這種狀況下,hook的有效期。
  3. 若是實在以爲麻煩,那能夠在須要使用Aspects和KVO共存的地方,直接換成在+(void)load中對方法進行替換實現對應功能。
相關文章
相關標籤/搜索