[iOS 理解] 为什么 objc_msgSend 必须用汇编实现

看 runtime 源码时,一个老生常谈的问题,objc_msgSend 是使用汇编实现的,为什么不使用 C 实现?

为了解决问题,我们直接复现问题:使用 C 实现一个简化的 objc_msgSend,暂不考虑性能、通用性,就是实现一个无参、无返回值的消息发送。

尝试

新建 main.mm

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

@interface Test: NSObject
@end

@implementation Test
- (void)testSth {
    NSLog(@"ok\n");
}
@end

// 声明一下我们即将实现的消息函数
void msgSend(void);

int main(int argc, const char * argv[]) {
    id s1 = [Test new];
	// 调用自定义的 msgSend
    auto ptr = (void(*)(id, const char*))msgSend;
    ptr(s1, "testSth");
     
    return 0;
}

实现 msgSend 函数

objc_msgSend 汇编逻辑在这篇文章已说明,现在实现一个简单的发送函数。

直接在 main.mm 后面开始写,注意我的电脑是 x86_64,如果是 arm mac 需要看注释适配

#   define ISA_MASK        0x00007ffffffffff8ULL
// arm 使用: #   define ISA_MASK        0x0000000ffffffff8ULL

void msgSend(void) {
    int64_t receiver;
    char *sel;
	// 取出当前的 self 和 方法名
	// for arm,x0 is self,x1 is SEL;
	// for x86,rdi is self,rsi is SEL
    __asm {
        mov receiver, rdi
        mov sel, rsi
    }
    // 获得 isa bits
    objc_object *receiver_ptr = (objc_object *)receiver;
    int64_t isaBits = (int64_t)receiver_ptr->isa;
    isaBits &= ISA_MASK;
    // 获得 Class 对象
    objc_object *classPtr = (objc_object *)isaBits;
    Class ClassObj = (__bridge Class)classPtr;
	
	// 接下来要获取 imp,没必要费功夫去 cache 结构、方法列表查询,
	// 为了方便直接用已有的 class_getMethodImplementation 查询

    // 系统所有的 SEL(const char*) 都存在一个哈希表内,只存一份。
    // 查询方法列表中是否有某个 SEL,为了速度 并不会比较字符串,而是直接比较地址,
    // 尽管两个地址上是同样的字符串内容。
    // 方法列表中的 SEL 就是全局哈希表中的 SEL。
    // 因此如果想调用 class_getMethodImplementation,必须得到唯一的那个 SEL 作为参数。
    // 下面两个方法本质相同,返回唯一的 SEL
    auto realSEL = sel_registerName(sel);
//    auto realSEL = NSSelectorFromString([NSString stringWithCString:sel]);

    IMP imp = class_getMethodImplementation(ClassObj, realSEL);
    auto ptr = (void(*)(objc_object *, char*))imp;
    // 把原来的两个参数放进去,就像是 msgSend 等同于目标 IMP 一样
    ptr(receiver_ptr, sel);
    // 如果这个方法有多个参数呢?我们如何知道参数数量和类型去强制转换 ptr?
    // 通过 method_getTypeEncoding 可以得到返回值和参数类型,
    // 接下来要按照 参数数量、类型、返回值类型 穷举所有组合,
    // 因为变长参数的存在,理论上有无穷个组合,无法实现。即使只允许有限个参数,也需要上百个 switch case
    
    // 如何解决?
    // 我们整个过程其实只是想得到 isa,然后得到 class 对象里面的方法列表,然后匹配 SEL,
    // 并不需要知道参数!
    
    // 如果用汇编实现 objc_msgSend(核心的查找 IMP 的逻辑),
    // 并在这个过程前后保持参数寄存器、栈内容不变
    // (只使用 ABI 中传参用不到的寄存器或者用栈暂存寄存器)
    // 获取到 imp 后,直接 call(br) imp,此时所有原参数都原封不动传给了目标函数,
    // 目标函数照常按照 ABI 取参数执行。
}

结论

上面代码最后一段已经说明,总结就是:
变长参数,使用 C 需要无数个 case,无法一一穷举,所以不可以使用 C。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值