关于 Method Swizzling 的一点思考

栏目: Objective-C · 发布时间: 6年前

内容简介:经典的实现例子:关于这个例子,笔者有几点疑问:查找资料后,给出以下回答。

经典的实现例子:

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // 如果交换的是类方法,则使用以下代码:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end
复制代码

关于这个例子,笔者有几点疑问:

method_exchangeImplementations

查找资料后,给出以下回答。

为什么可以交换?

Objc 中对象调用方法,被称为消息传递,其基本过程:

  1. 根据对象的 isa 指针,找到类。
  2. 在类的 objc_cachemethod_list 中,根据 method name 寻找对应方法。
  3. 若没有找到,则在其父类中寻找,直到 NSObject。
  4. 若是在 NSObject 中没有找到,则触发消息转发机制;若找到,则跳转到 method 中的 imp 指向的方法实现。
  5. 若消息转发机制也没能处理,则返回 unreconized selector。

结合 runtime 代码(简化后),理解上述过程。

// 消息传递
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...);

// 类
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    struct objc_method_list * _Nullable * _Nullable methodLists;
    struct objc_cache * _Nonnull cache;
}

// 方法
struct method_t {
    SEL name;
    const char *types;
    IMP imp;
}

// IMP 的声明
id (*IMP)(id, SEL, ...)
复制代码

可以看出,要想修改方法的实现,只需要修改 imp,因为它指向了方法的实现。

又得益于 Objc 的 Runtime System,在运行期,可以向类中新增或替换特定方法实现。

所以在 Objc 中实现方法交换,并不是一件很难的事。

为什么要在 load 方法里处理?

initialize 和 load 方法,都会自动调用,所以交换方法,可在二者选其一。

官方文档里对 load 的解释:

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

即当类或分类被加载时,load 方法就会被调用。

而对于 Initialize:

Initializes the class before it receives its first message.

即在类被发送第一条消息时,runtime system 会对该类发送一个 initialize() 的消息。

显然,若是在分类中的 load 实现方法交换,有2个好处:

  1. 可以在加载时就处理。
  2. 不用去修改原有代码。

为确保在不同线程中,处理代码只执行一次,需要借助 dispatch_once

为什么不直接使用 method_exchangeImplementations 即可?

实现例子里,先调用了 class_addMethod ,再根据其结果使用 class_replaceMethodmethod_exchangeImplementations

为何多此一举,不直接使用 method_exchangeImplementations 呢?

原因是被交换的方法,有可能没在本类中实现,而是在其父类中实现,此时,就需要将其加入到本类中。

所以才有了这样的代码:

// 添加 originalSelector 对应的方法
// 注意代码实现的效果是:originalSelector -> swizzledMethod
// 若是方法已经存在,则 didAddMethod 为 NO
BOOL didAddMethod = class_addMethod(class,
    originalSelector,
    method_getImplementation(swizzledMethod),
    method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
    // originalMethod 在上面添加成功了
    // 下面代码实现: swizzledSelector -> originalMethod
class_replaceMethod(class,
    swizzledSelector,
    method_getImplementation(originalMethod),
    method_getTypeEncoding(originalMethod));
} else {
    // 方法已经存在,直接交换
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
复制代码

思考题

假设有两个分类,都在 load 里,进行了同样的方法交换,那么再调用原来的方法,结果会是如何呢?

以下为简单的代码例子:

@implementation Cat
- (void)run {
    NSLog(@"ori");
}
@end

@impelmentation Cat (A)
// load 里将 run 交换成 a_run

- (void)a_run {
    [self a_run];
    NSLog(@"A");
}
@end

@impelmentation Cat (B)
// load 里将 run 交换成 a_run

- (void)a_run {
    [self a_run];
    NSLog(@"B");
}
@end

// 执行以下代码,会得到什么结果呢?
[cat run]; // result: ori
复制代码

结果也在代码里。

认真想想,很容易理解,处理了两次,又交换回来了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Practical JavaScript, DOM Scripting and Ajax Projects

Practical JavaScript, DOM Scripting and Ajax Projects

Frank Zammetti / Apress / April 16, 2007 / $44.99

http://www.amazon.com/exec/obidos/tg/detail/-/1590598164/ Book Description Practical JavaScript, DOM, and Ajax Projects is ideal for web developers already experienced in JavaScript who want to ......一起来看看 《Practical JavaScript, DOM Scripting and Ajax Projects》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码