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中对方法进行替换实现对应功能。
相关文章
相关标签/搜索