Objective-c 初阶 —— Runtime(方法交换 & 消息传递)

发布于:2025-07-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、消息传递

1、什么是消息

[a func1];

我们会把这种用方括号来调函数的方式称为发消息。对于这个例子,就相当于我们给 a 这个对象发了个 func1 的消息(个人认为指令更好理解)。

2、什么是 selector

selector 就是一个函数区分器。它只会给这个方法名一个唯一的哈希值。也就是说,如果只要两个函数的方法名是一样的,那么他们的 selector 的哈希值就是一样的。

3、什么是 isa 指针

每个对象都有一个 isa 指针。

实例对象的 isa 指针指向的是该实例所属的类对象,而类对象的 isa 指针指向元类对象。

那么如何区分类对象和元类对象呢?类对象存的是实例方法,不存类方法,而元类存的是类方法。

4、消息传递过程

1. 当一个方法要传给一个实例对象,那么 runtime 系统就会通过这个实例对象的 isa 指针找到该对象属于哪个类对象

2. 在这个类对象里的 dispatch table 找有没有符合当前 selector 的函数实现

3. 如果有,直接调用。

4. 如果没有,就不断地沿着这个类对象的 superclass 指针一路沿着继承链向上找,直到找到符合的函数实现或找到 NSObject 类。

5. 如果直到 NSObject 类都没找到,就进入“消息转发”的环节。

根据 selector 找对应的函数实现,runtime 做了一个小小的优化。就是给每个类对象一个缓存。这个缓存存的是在该类找过的实例方法实现和该类通过继承得来的函数实现的地址。于是在找函数实现时,runtime 会先在这个类的缓存里找。如果没找到才去这个类的 dispatch table 里找;再找不到就沿着继承链往上找。

二、消息转发的过程

1、什么是消息转发

消息转发就是当 runtime 在继承链中找不到 selector 对应的函数实现后,就会进行消息转发,即用其他对象来调这个方法。如果没有其他类能成功调用这个方法,才会报错。所以可以理解成消息转发就是报错前的最后一道防线。

2、消息转发的过程

2.1. 动态方法解析(resolveInstanceMethod:)

+ (BOOL)resolveInstanceMethod:(SEL)selector //实例对象的动态方法解析调

+ (BOOL)resolveClassMethod:(SEL)selector //类对象的动态方法解析调
@implementation Person

// 动态方法解析入口:当找不到实例方法 sayHello 时会调用这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(sayHello)) {
        // 使用 class_addMethod 动态添加方法实现
        class_addMethod(self, sel, (IMP)dynamicSayHello, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// 动态添加的方法实现
void dynamicSayHello(id self, SEL _cmd) {
    NSLog(@"Hello from dynamic method!");
}

@end

 其中 selector 是未处理的方法。 返回值表示能否新增一个方法来处理。如果可以,返回 YES;否则就看看基类能不能处理这个 selector。

2.2. 备用接收者

- (id)forwardingTargetForSelector:(SEL)aSelector;
// 备用接收者类
@interface BackupHandler : NSObject
- (void)sayHello;
@end

@implementation BackupHandler
- (void)sayHello {
    NSLog(@"👉 BackupHandler 收到了 sayHello 消息!");
}
@end

// 原始类:没有实现 sayHello,但可以转发给 backup
@interface MainObject : NSObject
@property (nonatomic, strong) BackupHandler *backup;
@end

@implementation MainObject

// 快速消息转发:返回备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sayHello)) {
        return self.backup; // 转发给备用对象
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

 这里 selector 也是未处理的方法。返回值为当前找到的备援接受者,如果没有则返回nil,进入下一阶段。

2.3. 完整消息转发

- (void)forwardInvocation:(NSInvocation *)anInvocation;
// 目标处理者类
@interface RealHandler : NSObject
- (void)sayHello;
@end

@implementation RealHandler
- (void)sayHello {
    NSLog(@"✅ RealHandler 处理了 sayHello 方法!");
}
@end

// 原始类:完全不实现 sayHello
@interface MainObject : NSObject
@property (nonatomic, strong) RealHandler *handler;
@end

@implementation MainObject

// 第一步:提供方法签名,告诉 runtime 方法是怎样的
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(sayHello)) {
        // v@: → 返回 void, 参数是 self 和 _cmd
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// 第二步:收到完整调用对象,自己决定怎么处理
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    if ([self.handler respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self.handler]; // 手动转发
    } else {
        [super forwardInvocation:anInvocation]; // 没法处理就崩溃
    }
}

@end

 其中 anInvocation 里面有原来消息的接收者、selector 、全部实参、返回值。

2.4. 报错中断

三、方法交换

1、dispatch table

每个类结构都包括以下两个基本元素:指向基类的指针 & dispatch table。其中 dispatch table 就是一个 2 列的函数表,左边是某个函数的 selector,右边是这个函数的具体实现的地址。 

2、方法交换

本质就是把 dispatch table 里的两个 address 的值交换。


网站公告

今日签到

点亮在社区的每一天
去签到