深入理解 Objective-C 方法调度机制

161 阅读3分钟

在 Objective-C 中,方法的调用并不像 C 语言那样是直接的函数调用,而是通过 消息传递(Message Passing) 机制完成的。这个特性使得 Objective-C 具有强大的动态性,但同时也影响了性能。本文将深入探讨 Objective-C 方法调用的底层实现,并提供相关示例代码。


1. Objective-C 方法调用的本质

在 C 语言中,调用一个函数时,编译器会直接生成一个跳转到目标函数地址的指令。但在 Objective-C 中,方法调用是以消息传递的形式进行的,即 objc_msgSend 函数负责方法的分发。

示例代码:

#import <Foundation/Foundation.h> #import <objc/runtime.h>

@interface Person : NSObject

  • (void)sayHello;

@end

@implementation Person

  • (void)sayHello {

    NSLog(@"Hello, World!"); }

@end

int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init];

    // 直接调用
    [p sayHello];

    // 等价于
    objc_msgSend(p, @selector(sayHello));
}
return 0;

}


在这段代码中,`[p sayHello]` 实际上会被编译器转换为 `objc_msgSend(p, @selector(sayHello))`。  

---

### **2. `objc_msgSend` 的执行流程**  

`objc_msgSend` 的执行流程大致如下:  

1. **查找方法的 IMP 指针(方法实现地址)**  
   - 在对象的 **isa 指针** 指向的类中查找方法的实现。  
   - `isa` 指针指向的是 **类对象(Class)**,类对象中存储着 **方法列表(Method List)**   - 如果在当前类找不到,就会沿着 **继承链** 向上查找,直到 `NSObject`2. **调用方法实现**  
   - 找到方法的 `IMP` 指针后,直接跳转执行。  
   - 如果找不到,会进入 **消息转发(Message Forwarding)** 机制。  

可以使用 `class_getMethodImplementation` 来获取方法的 `IMP` 指针:  

#import <objc/runtime.h>

IMP imp = class_getMethodImplementation([Person class], @selector(sayHello));
imp();

3. 方法缓存(Method Cache)优化

由于每次方法调用都要查找 IMP,如果类的继承关系较复杂,查找开销会很大。为此,Objective-C 运行时提供了 方法缓存(Method Cache) 机制。

  • 缓存策略
    • 运行时会在 Class 结构体中维护一个 缓存表(Cache),存储最近调用的方法。
    • 每次方法调用时,会先查找缓存,若命中,则直接调用 IMP,否则进入 Method List 查找并缓存到 Cache 中。
    • 这样可以大幅提升方法调用的效率。

可以通过 class_getInstanceMethod 来查看 Method List

Method method = class_getInstanceMethod([Person class], @selector(sayHello)); NSLog(@"Method name: %s", sel_getName(method_getName(method)));


---

### **4. 消息转发(Message Forwarding)机制**  

当 `objc_msgSend` 无法找到方法时,会触发 **消息转发机制**,它包括三个阶段:  

#### **(1)动态方法解析**  

在方法查找失败后,首先调用 `+resolveInstanceMethod:` 允许动态添加方法。  

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@"动态添加的方法被调用!");
}

@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(sayHello)) {
        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

(2)备用接收者(Fast Forwarding)

如果动态方法解析失败,则进入 快速转发,可以使用 -forwardingTargetForSelector: 让另一个对象处理该方法。

@interface Dog : NSObject

  • (void)bark; @end

@implementation Dog

  • (void)bark { NSLog(@"狗在叫!"); }

@end

@implementation Person

  • (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(sayHello)) { return [[Dog alloc] init]; // 转发给 Dog 处理 } return [super forwardingTargetForSelector:aSelector]; }

@end


#### **(3)完整消息转发(Full Forwarding)**  

如果 `-forwardingTargetForSelector:` 返回 `nil`,则进入 **完整消息转发** 机制:  

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

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"完整消息转发:无法找到方法 %@", NSStringFromSelector(anInvocation.selector));
}

5. 总结

  • Objective-C 方法调用是通过 消息传递 机制实现的,即 objc_msgSend 负责方法分发。
  • objc_msgSend 通过 isa 指针在 方法缓存(Cache)方法列表(Method List) 查找 IMP,提升性能。
  • 如果找不到方法,会触发 消息转发机制,包括:动态方法解析、备用接收者、完整消息转发。
  • 了解这些机制对于优化性能、避免 unrecognized selector 崩溃、实现动态特性(如方法交换)非常重要。

推荐阅读