正在加载今日诗词....
5 min read

iOS Method Swizzling

iOS 语言动态之 方法交互 Method Swizzling, 谨慎使用 ,保你不被祭天!
iOS Method Swizzling

前言

iOS 开发过程中,经常会有类似方法统一拦截的处理, 比如 神策埋点 中的 `AutoTrack` , 它的底层原理就是利用 `Runtime` 中的黑魔法 `Method Swizzling` . 
但是这种方式, 虽然用起来简单, 像 `Spring` 中的 `AOP` 一样, 无侵入完成业务逻辑,确实很美,但不要想的太美! 

一般我们都是通过 `Category`  装饰模式给已有的类增加功能, 那么就要了解到 `Category` 的特性, 才能更好的理解 黑魔法的危害性. 

本文主要是对 iOS 界的毒瘤 这篇文章的解读, 像是洗稿一样, 哈哈,在前人的肩膀上更快地成长, 如果不喜欢,欢迎反馈.

issue of method swizzling

RSSwizzle 指出 Classical method swizzling with method_exchangeImplementations is quite simple, but it has a lot of limitations:

  • It is safe only if swizzling is done in the +load method. If you need to swizzle methods during application's lifetime you should take into account that third-party code may do swizzling of the same method in another thread at the same time.

    • + (void)load+ (void)initialize (在这个方法里面调用其实很尴尬, 有可能这个类不调用,不过不调用的话,交互用来干嘛) 中去执行方法交换, 且保证只执行一次,线程安全
  • The swizzled method must be implemented by the class itself and not by superclasses. Workarounds by copying implementation from the superclass do not really work. Original implementation in the superclass must be fetched at the time of calling, not at the time of swizzling (1,2).

    • 保证仅交换当前类的方法,而不是父类的
  • The swizzled method implementation must not rely on the _cmd argument. (And generally you can not be sure in it (5).)

    • 最好不要依赖 _cmd
  • Naming conflicts are possible (3).

    • 防止命名冲突造成的循环调用

    当然过程中自己也是手敲了自己的 示例Demo runtime-issue , 以加深对不同顺序 hook 所造成的运行结果 有自己的理解.

子类分类优先父类分类的 hook 会导致问题

sub-hook-before-super-hook-792aff08-8403-40de-ad36-7f798c2565b3-1560514605853-47689281

父类分类优先于子类分类的 hook 没有问题

super-hook-before-sub-hook-fe774705-01da-4cda-b0d2-d477959b2773-1560514642901-40508111

runtime knowledge

method-swizzling 为了方便对 Method Swizzling 的理解, 首先要理解的就是 相关的概念定义,以及相关方法执行时的注意事项.

  • Selector(typedef struct objc_selector *SEL):在运行时 Selectors 用来代表一个方法的名字。Selector 是一个在运行时被注册(或映射)的C类型字符串。Selector由编译器产生并且在当类被加载进内存时由运行时自动进行名字和实现的映射。
  • Method(typedef struct objc_method *Method):方法是一个不透明的用来代表一个方法的定义的类型。
  • Implementation(typedef id (*IMP)(id, SEL,...)):这个数据类型指向一个 方法的实现的最开始的地方。该方法为当前CPU架构使用标准的C方法调用来实现。该方法的第一个参数指向调用方法的自身(即内存中类的实例对象,若是调用类方法,该指针则是指向元类对象 metaclass )。第二个参数是这个方法的名字 selector,该方法的真正参数紧随其后。

总体来说是 SEL -> Method -> IMP ; Method 关联了 SELIMP

Methodd定义

typedef struct objc_method *Method;

A pointer to the  Method data structure that corresponds to the implementation of the 
*  selector specified by aSelector for the class specified by aClass, or NULL if the specified 
*  class or its superclasses do not contain an instance method with the specified selector

Methodd 结构体定义

struct objc_method {
SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
char * _Nullable method_types                            OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}  

类 结构体定义

struct objc_class {
Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class                              OBJC2_UNAVAILABLE;
const char * _Nonnull name                               OBJC2_UNAVAILABLE;
long version                                             OBJC2_UNAVAILABLE;
long info                                                OBJC2_UNAVAILABLE;
long instance_size                                       OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

  • Method origMethod = class_getInstanceMethod(self, origSel_);
    • 理解 子类 中的 Method 是如何获取的
    • This function searches superclasses for implementations, whereas class_copyMethodList does not
BOOL origAdd = class_addMethod(self,
					origSel_,
					class_getMethodImplementation(self, origSel_),
					method_getTypeEncoding(origMethod));
  • 理解 class_addMethod 添加是否成功的 原因
    class_addMethod will add an override of a superclass's implementation,
    but will not replace an existing implementation in this class.
    To change an existing implementation, use method_setImplementation
     意味着, 子类methodlist中如果没有这个 Method, 则会添加一个 Method, 在 methodlist中, 不会替换父类中的 Method.
  • method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
    • 理解 方法交换的本质是 什么
    IMP imp1 = method_getImplementation(m1); 读取结构体成员变量 m1-> Imp (imp1) 
    IMP imp2 = method_getImplementation(m2);
    method_setImplementation(m1, imp2); 设置结构体成员变量  m1-> Imp (imp2)
    method_setImplementation(m2, imp1);

做到以上几个问题, 再仔细比对方法交换中的每一步,脑子想不明白,就动动手 😝.

应用

UITableView

更安全的方式

RSSwizzle 神器, 奈何使用宏的调用方式, 大家都不喜欢, 更多的人是使用 Aspect 更 OC 哈哈

不过要说安全还是 RSSwizzle

加油 💪, 后面再一步一步学习一下库吧