实战中的isa swizzling的小结

1,503 阅读2分钟

1. KVO的基础原理分析

略过, 相关内容很多

2. Aspects 的基础原理分析

本文不贴源码, 只梳理逻辑, 这里只分析实例的hook, 具体流程如下:

例如需要替换类Person的实例person的setName:方法, Aspects中处理的方式如下:

  1. 创建动态子类 Person_Aspects_, 它的superClass指向Person
  2. 替换动态子类Person_Aspects_-(Class)class+(Class)class, 类似KVO的isa隐藏实现, 返回Person的对应的SEL的IMP
  3. 替换动态子类Person_Aspects_-(void)forwardInvocation:(NSInvocation *)anInvocationSEL对应的IMP实现为__ASPECTS_ARE_BEING_CALLED__
  4. 给动态子类Person_Aspects_增加-setName_aspects_:,该SEL的IMP是-setName:的IMP, 同时将-setName:的IMP替换成_objc_msgForward
  5. 修改person的isa指针指向Person_Aspects

此时调用, [person setName:xxx]方法时, 直接调用_objc_msgForward进入消息转发流程会调用Person_Aspects_-forwardInvocation:方法, 底层IMP指针是__ASPECTS_ARE_BEING_CALLED__, 在这个实现中去进行AOP切面处理!!!

关键技术点:

  1. block的内部结构, 尤其Block的MethodSignature, isa处理!
  2. 封装AspectIdentifier封装需要hook的selector,block,blockSignature,object等全部信息
  3. 使用 alias_selector构造Associated关联对象, 持有容器对象AspectContainer, 通过AspectContainer的容器持有AspectIdentifier
  4. void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) 方法中通过aliase_selector取出容器中的 AspectIdentifier对象,调用invokeWithInfo的中触发hook的block!!!

其他技术点:

  1. 使用dispatch_once单例构造全局黑名单
  2. oc消息查找与消息转发全流程

关于block的内部结构, 可以参考更多资源libclosure-79

3. 神策数据的isa HOOK 与KVO兼容

神策的埋点服务中, 针对 setDelegate:方法进行hook, 其中核心原理结合 isa swizzling 和 method swizzling, 以及兼容 NSProxy/NSObject 对象作为delegate 的场景, 并通过对象是NSProxy/NSObject来决定兼容KVO的处理:

  1. 使用关联对象sensorsdata_superClass标记是否已经被hook处理过
  2. 判断是否被系统KVO过, 如果进行过KVO, 那么此时对象的类型是 KVONotifying_WBTVDelegate
    1. 获取realClass, WBTVDelegate
    2. 直接在KVONotifying_WBTVDelegate类中添加方法removeObserver:forKeyPath:
      1. hook的removeObserver:forKeyPath:中, 移除Observer以后, 重新走1的流程, 重新HOOK
    3. 并在KVONotifying_WBTVDelegate类中添加方法hook 的 selector方法, 在hook的delegate方法中实现进行AOP打点
  3. 创建动态子类增加前缀__CN.SENSORSDATA.序号.WBTVDelegate
  4. 在动态子类中增加需要hook selector, 并实现IMP, 在IMP中打点
  5. 判断delegate object是否NSObject,
    1. 如果是, 那么需要兼容KVO
    2. __CN.SENSORSDATA.序号.WBTVDelegate中增加addObserver:forKeyPath:options:context:实现
    3. addObserver:forKeyPath:options:context:中先调用原系统KVO, 然后重写__CN.SENSORSDATA.序号.WBTVDelegate-class方法, 隐藏系统KVO与动态子类
  6. 注册动态子类, 设置isa swizzling
  7. 新建一个关联对象SADelegateProxyParasite设置给delegate object, 用来监听delegate objectdealloc调用, 此时调用disposeClass, 清理动态子类__CN.SENSORSDATA.序号.WBTVDelegate

神策这套isa swizzling实现, 可以兼容NSProxy代理链, 也能兼容 NSObject的KVO

参考

  1. github.com/steipete/As…
  2. github.com/sensorsdata…