johnhao
johnhao
发布于 2017-01-11 / 3 阅读
0

iOS-OC的消息机制

Objective-C(OC)的消息机制是其最核心、最具特色的运行时特性之一。与 C++ 或 Java 等语言在编译期绑定方法调用不同,OC 的方法调用是在运行时动态解析的,这种机制称为 消息传递(Message Passing)


一、什么是消息机制?

在 OC 中,调用一个方法实际上是在向对象发送一条消息。例如:

[receiver doSomething];

这行代码在编译时不会直接生成函数调用指令,而是被转换为对运行时函数 objc_msgSend 的调用:

objc_msgSend(receiver, @selector(doSomething));

关键点:方法调用 = 发送消息 → 运行时动态查找实现(IMP)


二、消息发送的完整流程

当向一个对象发送消息时,Runtime 会按以下顺序处理:

1. 快速查找(Fast Path)

  • 通过 receiver->isa 获取类。

  • 在该类的 方法缓存(cache_t) 中查找 SEL(方法名)对应的实现(IMP)。

  • 如果命中缓存(常见于频繁调用的方法),直接跳转执行,效率极高。

2. 慢速查找(Slow Path)

如果缓存未命中:

  • 遍历当前类的 方法列表(method list)

  • 若未找到,则通过 class->superclass 沿着继承链向上查找(父类 → 祖父类 → … → NSObject)。

  • 找到后,将方法加入缓存,再调用。

3. 动态方法解析(Dynamic Method Resolution)

如果继承链中都找不到该方法:

  • Runtime 会调用 +resolveInstanceMethod:(实例方法)或 +resolveClassMethod:(类方法)。

  • 开发者可在此阶段动态添加方法实现(使用 class_addMethod)。

+ (bool)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(dynamicMethod:)) {
        class_addMethod(self, sel, (IMP)dynamicImplementation, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

4. 消息转发(Message Forwarding)

如果仍无法解析,则进入转发阶段,分两步:

a. 备援接收者(Fast Forwarding)

  • 调用 -forwardingTargetForSelector:

  • 可返回另一个能响应该消息的对象,Runtime 会直接将消息转发给它。

  • 优点:开销小,不创建 NSInvocation

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(specialMethod)) {
        return self.helperObject; // 由 helper 处理
    }
    return [super forwardingTargetForSelector:aSelector];
}

b. 完整消息转发(Full Forwarding)

如果上一步返回 nil,则:

  1. 调用 -methodSignatureForSelector: 获取方法签名(必须实现,否则 crash)。

  2. 创建 NSInvocation 对象,封装 target、selector、参数。

  3. 调用 -forwardInvocation:,开发者可自定义处理逻辑(如记录日志、网络调用等)。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (/* 可处理 */) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    if ([self.helper respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self.helper];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

⚠️ 如果以上所有步骤都失败,程序会抛出 unrecognized selector sent to instance 异常并 crash。


三、消息机制的核心数据结构

结构

说明

objc_object

所有对象的基类,包含 isa 指针

objc_class

类结构体,包含 isasuperclasscachemethods

SEL

方法选择器(selector),本质是 C 字符串(如 "doSomething"

IMP

方法实现指针,类型为 id (*IMP)(id, SEL, ...)

Method

方法结构体,包含 SELIMP、类型编码(types)


四、消息机制的优势与代价

✅ 优势

  • 高度动态性:支持运行时添加/替换方法(Method Swizzling)、KVO、Core Data 等。

  • 灵活转发:可实现代理、消息代理、远程调用等高级模式。

  • 解耦设计:发送者无需知道接收者是否能响应消息。

❌ 代价

  • 性能开销:相比静态调用,消息发送有查找、缓存、可能的转发开销。

  • 安全性降低:编译器无法检查方法是否存在(需依赖 Clang 警告或静态分析)。

💡 优化:现代 Runtime 使用 缓存(bucket hash table)tagged pointer / inline cache 技术,使消息发送速度接近虚函数调用。


五、常见应用场景

  1. KVO(Key-Value Observing):通过 isa-swizzling 动态生成子类并重写 setter。

  2. Method Swizzling:交换方法实现,用于埋点、日志、修复 bug。

  3. 响应式编程(如 ReactiveCocoa):利用消息转发实现信号链。

  4. JSON 模型转换:通过 setValue:forKey: 利用 KVC(基于消息机制)自动赋值。


六、总结

OC 的消息机制 = 动态绑定 + 缓存优化 + 失败容错(解析/转发)

流程图简化如下:

[receiver message]
       ↓
objc_msgSend(receiver, SEL)
       ↓
1. 查缓存 → 命中?→ 执行 IMP
       ↓ 否
2. 查方法列表 → 找到?→ 缓存 + 执行
       ↓ 否
3. 沿 superclass 链向上查找
       ↓ 未找到
4. +resolveInstanceMethod:
       ↓ 未解决
5. -forwardingTargetForSelector:
       ↓ 返回 nil
6. -methodSignatureForSelector: → 创建 NSInvocation
       ↓
7. -forwardInvocation:
       ↓ 仍失败
8. unrecognized selector exception → crash

理解消息机制,是掌握 Objective-C 高级特性和调试疑难问题的关键。