iOS 方法交换后为什么调自身?

在 iOS 开发中,方法交换(Method Swizzling)是一种强大的技术,它允许开发者在运行时替换类中的方法实现,以便在调用某个方法时添加自定义逻辑。然而,方法交换后,开发者可能会发现调用原方法时,仍然会触发自身的实现。本文将探讨这一现象的原理,并提供代码示例以及对应的序列图和流程图。

方法交换的原理

在 Objective-C 中,方法的实现是通过选择器(Selector)来找到的。当我们使用 Method Swizzling 时,实际上是交换了选择器与其对应的方法实现。这样,一旦调用了某个方法,实际上调用的是我们交换后的方法实现。

示例代码

下面是一个简单的示例,演示如何通过方法交换来替换 viewDidLoad 方法:

#import <objc/runtime.h>

@implementation MyViewController

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(my_viewDidLoad);
        
        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
        
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)my_viewDidLoad {
    // 在原本的 viewDidLoad 中添加自定义逻辑
    [self my_viewDidLoad]; // 这里调用的是原始的 viewDidLoad 方法
    NSLog(@"View Did Load Swizzled");
}

@end

在这个示例中,当 viewDidLoad 被调用时,实际会调用 my_viewDidLoad 实现,而在该实现中又调用了 my_viewDidLoad 造成递归,最终导致程序崩溃。原因在于,方法交换后,my_viewDidLoad 实际上调用的是 viewDidLoad

方法交换后的自调用现象

当你进行方法交换时,开发者需要谨慎处理自调用的问题。由于方法本质上仍然是同一个选择器,导致在调用新实现时,可能会再次调用新实现造成无限递归。因此,如果要确保能在新实现中调用原实现,必须使用一个透明的方法名,以避免调用到新方法。

流程图

下面是一个描述方法交换过程的流程图:

flowchart TD
    A[开始] --> B[获取原始方法与交换方法]
    B --> C[执行方法交换]
    C --> D{调用原始方法}
    D -->|是| E[调用新实现]
    E --> F{是否再调用}
    F -->|是| G[递归调用]
    G --> H[崩溃]
    F -->|否| I[结束]
    D -->|否| I

序列图

下面是对应的序列图,描述了方法交换后调用过程:

sequenceDiagram
    participant VC as MyViewController
    participant OR as Original Method
    participant SW as Swizzled Method

    VC->>SW: call viewDidLoad()
    SW->>OR: call my_viewDidLoad() (original)
    OR->>SW: call viewDidLoad() (swizzled)
    SW->>OR: call my_viewDidLoad() (ending up recursive)

结论

方法交换是一个强大的工具,在提升代码灵活性和实现某些特殊功能方面尤为重要。然而,开发者在使用方法交换时需谨慎,以防止因递归调用造成不必要的崩溃。通过理解方法交换的原理和注意事项,我们可以更好地利用这一技术,开发出更加灵活和高效的 iOS 应用。