首页 区块链

Objective-C Runtime:方法交换与消息传递机制深度解析

分类:区块链
字数: (0020)
阅读: (1484)
内容摘要:Objective-C Runtime:方法交换与消息传递机制深度解析,

在 Objective-C 的世界里,Runtime 扮演着至关重要的角色。它不仅是 OC 面向对象特性的基石,更是实现动态性、灵活性的关键。今天我们聚焦 Objective-C 初阶 中两个核心概念:方法交换 (Method Swizzling)消息传递 (Message Passing),深入探讨它们的工作原理、应用场景以及需要注意的坑。

方法交换 (Method Swizzling):偷偷摸摸换实现

问题场景重现

假设我们有一个第三方的 SDK,它提供了一个 UIButton 的子类 CustomButton,并且有一个 setTitle: 方法。现在我们需要在不修改 SDK 源码的情况下,为 setTitle: 方法添加额外的日志记录功能。这就是方法交换大显身手的时候。

Objective-C Runtime:方法交换与消息传递机制深度解析

底层原理剖析

方法交换的核心在于利用 Objective-CRuntime 机制,动态地改变方法的实现。具体来说,就是交换两个方法的 IMP (Implementation Pointer),也就是函数指针。这样,调用原本的方法名,实际上执行的是交换后的实现。

Objective-C Runtime:方法交换与消息传递机制深度解析

代码解决方案

#import <objc/runtime.h>

@implementation UIButton (Swizzling)

+ (void)load { // 在类加载时进行方法交换
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(setTitle:forState:);
        SEL swizzledSelector = @selector(my_setTitle:forState:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(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); // 交换两个方法的实现
        }
    });
}

// 我们自己的方法实现,添加了日志
- (void)my_setTitle:(NSString *)title forState:(UIControlState)state {
    NSLog(@"Setting title: %@", title); // 添加日志
    [self my_setTitle:title forState:state]; // 调用原始的 setTitle: 方法
}

@end

实战避坑经验总结

  • load 方法的线程安全: 使用 dispatch_once 确保方法交换只执行一次,避免多线程竞争导致的问题。
  • 命名冲突: 为 Swizzled 方法添加前缀,避免与 SDK 中其他方法冲突。例如,使用 my_ 或者类名的缩写。
  • 调用原始方法: 在 Swizzled 方法中一定要调用原始方法,否则会丢失原始功能。
  • 父类方法: 如果需要 Swizzle 父类的方法,需要特别小心。确保只 Swizzle 子类自身的方法,避免影响其他子类。
  • 谨慎使用: 方法交换是一种强大的技术,但过度使用会增加代码的复杂性,降低可维护性。

消息传递 (Message Passing):OC 的灵魂

问题场景重现

想象一下,你调用了一个对象的方法,但这个对象并没有实现这个方法。在静态语言中,这通常会导致编译错误。但是在 Objective-C 中,得益于消息传递机制,程序并不会立即崩溃,而是会尝试将这个消息转发给其他对象处理。

Objective-C Runtime:方法交换与消息传递机制深度解析

底层原理剖析

Objective-C 的消息传递机制基于 objc_msgSend 函数。当我们调用一个对象的方法时,实际上是调用了 objc_msgSend 函数,并将消息 (方法名) 和参数传递给它。Runtime 会根据对象的 isa 指针找到对应的类,然后在类的方法列表中查找对应的方法。如果找到了,就执行该方法;如果找不到,就会进入消息转发流程。

Objective-C Runtime:方法交换与消息传递机制深度解析

消息转发流程大致分为三个步骤:

  1. resolveInstanceMethod: / resolveClassMethod:: 询问类是否可以动态地为这个 selector 提供实现。
  2. forwardingTargetForSelector:: 如果第一步无法解决,则询问是否有其他对象可以处理这个消息。如果返回一个对象,则将消息转发给该对象。
  3. methodSignatureForSelector:forwardInvocation:: 如果第二步也无法解决,则会调用 methodSignatureForSelector: 方法获取方法签名,然后调用 forwardInvocation: 方法进行完整的消息转发。我们可以在 forwardInvocation: 方法中实现自定义的消息转发逻辑。

代码解决方案

以下是一个简单的消息转发的例子:

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
- (void)unknownMethod;
@end

@interface AnotherClass : NSObject
- (void)handleUnknownMethod;
@end

@implementation AnotherClass
- (void)handleUnknownMethod {
    NSLog(@"AnotherClass handled the unknown method!");
}
@end

@implementation MyClass

// 第一步:尝试动态添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(unknownMethod)) {
        // 动态添加 handleUnknownMethod 的实现
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    } else {
        return [super resolveInstanceMethod:sel];
    }
}

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"Dynamic method implementation!");
}

// 第二步:尝试将消息转发给其他对象
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(unknownMethod)) {
        return [[AnotherClass alloc] init]; // 将消息转发给 AnotherClass
    } else {
        return [super forwardingTargetForSelector:aSelector];
    }
}

// 第三步:完整的消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    if ([[AnotherClass new] respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:[AnotherClass new]];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return signature;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *obj = [[MyClass alloc] init];
        [obj unknownMethod]; // 调用未实现的方法
    }
    return 0;
}

实战避坑经验总结

  • 避免无限循环: 在消息转发过程中,要避免出现无限循环。例如,A 对象将消息转发给 B 对象,B 对象又将消息转发回 A 对象,导致程序崩溃。
  • 性能损耗: 消息转发会带来一定的性能损耗,因为它需要多次查找方法列表。因此,应该尽量避免过度使用消息转发。
  • 方法签名:forwardInvocation: 方法中,需要手动创建方法签名。如果方法签名不正确,会导致程序崩溃。

希望这篇文章能帮助你更好地理解 Objective-C Runtime 中方法交换和消息传递这两个重要的概念。 掌握它们能让你在 OC 的世界里更加游刃有余,解决各种疑难杂症。 记得在使用 Objective-C Runtime 时,要特别小心,避免出现一些难以调试的问题。

Objective-C Runtime:方法交换与消息传递机制深度解析

转载请注明出处: 键盘上的咸鱼

本文的链接地址: http://m.acea1.store/blog/236107.SHTML

本文最后 发布于2026-04-12 09:39:43,已经过了15天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 向日葵的微笑 3 天前
    学习了,Runtime 果然是 OC 的精髓所在。感觉作者对 OC 的理解很深刻,膜拜大佬!
  • 冬天里的一把火 11 小时前
    方法交换这个技巧很实用,以前修改第三方库的时候就用过,避免了直接修改源码的麻烦。
  • 雪碧透心凉 4 天前
    方法交换这个技巧很实用,以前修改第三方库的时候就用过,避免了直接修改源码的麻烦。
  • 绿茶观察员 5 天前
    讲的真透彻,之前一直对 Runtime 的理解模模糊糊,现在清晰多了!
  • 薄荷味的夏天 1 天前
    消息转发那里,能不能再详细讲一下 `methodSignatureForSelector:` 的作用?不太明白。