在 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崩溃、实现动态特性(如方法交换)非常重要。
推荐阅读:
- Apple 官方文档:Objective-C Runtime Programming Guide
- 源码解析:Objective-C runtime 源码