一、消息发送的完整路径

在Objective-C中,方法的调用并非简单的函数跳转,而是一条经过精心设计的消息传递链路。理解这条链路,是掌握Runtime、解决疑难Bug、以及设计高效Hook机制的前提。

1.1 消息发送的汇编级追踪

// objc_msgSend的C签名(其实质是一个汇编函数)
// id objc_msgSend(id self, SEL op, ...);

// 消息发送的完整调用栈(从OC到IMP的路径)
//
// objc_msgSend(receiver, @selector(foo))
//   ├── [0] Check nil receiver(快速路径:nil直接返回)
//   ├── [1] CacheLookup(查找方法缓存)
//   │       └── 命中 → 直接跳转执行
//   ├── [2] _objc_msgSend_uncached(缓存未命中)
//   │       └── _class_lookupMethodAndLoadCache3
//   │           ├── lookupMethodForClass(从类的方法列表遍历)
//   │           └── 找到 → 写入缓存 → 跳转执行
//   │           └── 未找到 → 进入消息转发流程
//   └── [3] Method dispatch → IMP

// 为什么objc_msgSend要用汇编实现?
// ① 方法查找需要:self寄存器 + 方法选择器寄存器
// ② 多返回值约定:C函数无法同时返回两个值(IMP + 实现地址)
// ③ 行内缓存写入:避免函数调用开销(内联展开)
// ④ SIMD/浮点协议:不影响浮点寄存器状态

// 汇编实现(arm64简化版):
// ENTRY _objc_msgSend
//     ldp x0, x1, [x0]       // x0=receiver, x1=SEL
//     and x1, x1, #ISA_MASK  // x1=isa
//     LLookupImpCyclic:
//     sub  x2, x1, #_CACHE   // x2=cache
//     ldp x3, x4, [x2, #CACHE_MASK]  // x3=buckets, x4=occupied
// LSearchBucket:
//     cmp  x3, x1            // 对比SEL
//     b.eq LFound            // 找到则跳转执行
//     sub  x3, x3, #16      // 移动到下一个bucket
//     sub  x4, x4, #1        // 计数器-1
//     cbnz x4, LSearchBucket // 不为0继续搜索
//     b    LMiss              // 缓存miss

二、方法缓存机制与缓存污染

2.1 cache_t 的数据结构

// objc-cache.h 核心结构
struct cache_t {
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<uint16_t> _flags;
            uint16_t            _occupied;
        };
        explicit_atomic<uintptr_t> _maybeMask;
    };
};

// _bucketsAndMaybeMask 的位布局(arm64):
// 高位存buckets指针,低位存maybeMask(哈希掩码)
// buckets数组:每个bucket = { IMP, SEL }
// _occupied:当前已填充的bucket数量

// 缓存查找算法(开放寻址 + 线性探测)
static inline mask_t cache_t::mask()
{
    return _maybeMask.load(memory_order_relaxed);
}

static inline bucket_t * cache_t::buckets()
{
    uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
    return (bucket_t *)(addr & ~mask());
}

// 哈希选择器:
// static inline uint32_t hash(SEL sel, mask_t mask) {
//     uintptr_t s = (uintptr_t)sel;
//     return ((s >> 2) ^ (s >> 0)) & mask;
// }

// 缓存未命中时的填充(写入IMP和SEL)
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
    // ① 扩容条件:_occupied >= capacity * 3/4
    // ② 插入:直接覆盖已有slot(不考虑冲突)
    // ③ 标记:_occupied++
}

// ⚠️ 缓存污染的典型场景
// 1. 大量不同SEL写入 → 缓存频繁失效
// 2. 类簇(Class Cluster):[NSArray array] 返回 __NSArrayI vs __NSArrayM
//    两者各自维护独立缓存,消息传递路径不同
// 3. dealloc中向已释放对象发消息 → EXC_BAD_ACCESS

三、消息转发的完整四步流程

3.1 转发流程图

// 消息转发的四个阶段(按顺序依次尝试)
//
// ┌─────────────────────────────────────────────┐
// │ Step 1: +resolveInstanceMethod           │
// │         (+resolveClassMethod for class m) │
// │ 用途:动态添加方法实现                      │
// │ 返回:YES表示已处理,NO则进入Step 2        │
// └─────────────────────────────────────────────┘
//                    ↓
// ┌─────────────────────────────────────────────┐
// │ Step 2: -forwardingTargetForSelector      │
// │ 用途:更换消息接收者(快速转发)            │
// │ 返回:nil则进入Step 3                     │
// └─────────────────────────────────────────────┘
//                    ↓
// ┌─────────────────────────────────────────────┐
// │ Step 3: -methodSignatureForSelector        │
// │         +instanceMethods, +classMethods    │
// │ 用途:获取方法签名(为Step 4准备)         │
// │ 返回:nil则抛出异常                        │
// └─────────────────────────────────────────────┘
//                    ↓
// ┌─────────────────────────────────────────────┐
// │ Step 4: -forwardInvocation:               │
// │         NSInvocation包装的消息              │
// │ 用途:完全控制消息处理逻辑                  │
// └─────────────────────────────────────────────┘

3.2 各阶段实战代码

// 示例:实现一个"空响应"对象(类似null object pattern)
@interface NullHandler : NSObject
@end

@implementation NullHandler

// Step 1:动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    // 方式一:直接添加IMP(处理getter/setter)
    if (sel == @selector(getValue)) {
        class_addMethod([self class], sel,
            imp_implementationWithBlock(^(id self) {
                return @(-1); // 返回默认值
            }),
            "@@:"
        );
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// Step 2:快速转发(更换接收者)
- (id)forwardingTargetForSelector:(SEL)aSelector {
    // 将消息转发给另一个对象
    // 适用场景:委托模式、跨线程消息转发
    if ([self.target respondsToSelector:aSelector]) {
        return self.target; // nil表示不需要转发
    }
    return nil; // 进入Step 3
}

// Step 3:获取方法签名(必须精确)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    // 如果目标是NSObject,签名查询可以简单处理
    NSMethodSignature *sig = [super methodSignatureForSelector:sel];
    if (!sig) {
        // 手动构造签名:返回类型@,参数类型v@:
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return sig;
}

// Step 4:完整转发(最灵活,控制力最强)
- (void)forwardInvocation:(NSInvocation *)invocation {
    // 方案一:转发给其他对象(可多个)
    if ([self.target respondsToSelector:invocation.selector]) {
        [invocation invokeWithTarget:self.target];
    }

    // 方案二:日志记录未处理消息(调试利器)
    NSLog(@"[NullHandler] 未处理消息: %@ %@",
          NSStringFromSelector(invocation.selector),
          invocation.target);

    // 方案三:静默吞掉(完全不处理)
    // 不调用invoke,不会崩溃
}

@end

四、消息转发的性能代价与优化策略

// 性能对比:直接调用 vs 缓存命中 vs 完整转发
// 测试基准:MacBook Pro M1,百万次调用平均值
//
// 方法调用类型              耗时(纳秒/次)
// ─────────────────────────────────────
// 缓存命中 (inline cache)    ~1-2ns
// 完整消息转发 (4步)         ~800-2000ns ⚠️
// objc_msgSendSlowMiss       ~200-400ns
//
// 结论:完整转发代价是直接调用的 400-2000 倍

// ⚡ 优化策略一:缓存已转发的方法签名
static NSMutableDictionary *forwardedSelectorsCache;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    @synchronized (forwardedSelectorsCache) {
        if (forwardedSelectorsCache[@selector]) {
            return forwardedSelectorsCache[@selector];
        }
    }
    NSMethodSignature *sig = [super methodSignatureForSelector:sel];
    @synchronized (forwardedSelectorsCache) {
        forwardedSelectorsCache[@selector] = sig ?: [NSNull null];
    }
    return sig;
}

// ⚡ 优化策略二:避免在forwardInvocation中创建大对象
// ❌ 差:每次转发都创建NSData
- (void)forwardInvocation:(NSInvocation *)inv {
    self.largeData = [[NSData alloc] initWithContentsOfFile:path]; // 每次都分配
}

// ✅ 好:懒加载 + 复用
- (NSData *)lazyLargeData {
    if (!_largeData) _largeData = [[NSData alloc] initWithContentsOfFile:path];
    return _largeData;
}

// ⚡ 优化策略三:批量转发(装饰器模式)
// 适用于:多个对象需要相同转发逻辑
@interface ForwardingDecorator : NSObject
@property (nonatomic, strong) NSObject *target;
@property (nonatomic, strong) NSArray *handledSelectors;
- (instancetype)initWithTarget:(NSObject *)target
             handledSelectors:(NSArray *)sels;
@end

@implementation ForwardingDecorator
- (BOOL)respondsToSelector:(SEL)aSelector {
    return [self.handledSelectors containsObject:NSStringFromSelector(aSelector)]
        || [super respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.handledSelectors containsObject:NSStringFromSelector(aSelector)]) {
        return self.target;
    }
    return nil;
}
@end

五、消息转发的安全与逆向应用

// 🔐 场景一:防止被Hook(应用加固)
// 原理:Method Swizzling利用消息转发机制拦截调用
// 对策:直接替换IMP,绕过消息转发

// ❌ 易被Hook的方式(走消息转发)
- (void)secureOperation {
    // 这里可以被swizzling拦截
}

// ✅ 难被Hook的方式(直接IMP)
// 通过clang directive强制内联或直接调用C函数
__attribute__((noinline))
void secure_operation_impl() {
    // 直接机器码执行,不经过objc_msgSend
}

// 🔍 场景二:逆向工程中的消息转发利用
// 利用forwardInvocation实现Method Swizzling
@interface MethodSwizzler : NSObject
+ (void)swizzleMethod:(SEL)originalSel
           withMethod:(SEL)swizzledSel
             inClass:(Class)cls;
@end

@implementation MethodSwizzler
+ (void)swizzleMethod:(SEL)originalSel
           withMethod:(SEL)swizzledSel
             inClass:(Class)cls {
    Method original = class_getInstanceMethod(cls, originalSel);
    Method swizzled = class_getInstanceMethod(cls, swizzledSel);

    // addMethod:即使原方法不存在也添加成功(返回YES)
    // 这样可以给category添加IMP
    BOOL didAdd = class_addMethod(cls, originalSel,
        method_getImplementation(swizzled),
        method_getTypeEncoding(swizzled));

    if (didAdd) {
        class_replaceMethod(cls, swizzledSel,
            method_getImplementation(original),
            method_getTypeEncoding(original));
    } else {
        // 原方法存在,直接交换
        method_exchangeImplementations(original, swizzled);
    }
}
@end