iOS 底层原理 1 —— 简单解析 objc_msgSend

1. 初入宝地 - objc_msgSend 的作用

objc_msgSend 的作用就是根据两个参数------ self 和 selector 找到 IMP、并执行 IMP

selector:selector 是 SEL 的一个实例,是方法在运行时的标识符。 IMP :函数指针,就是函数执行的入口。 Method 对象就是函数对象,它是一个结构体,结构体包含 selector 和 IMP:
/// Method struct objc_method {

SEL method_name;

char *method_types;

IMP method_imp; };

objc_msgSend的伪代码如下:

objc 复制代码
id objc_msgSend(id self, SEL _cmd, ...){
    // 判空

    // 根据 isa 指针,找到类对象
    Class c = object_getClass(self);
    IMP imp = cache_lookup(c, _cmd);
    if(!imp)
        imp = class_getMethodImplementation(c, _cmd);
    return imp(self, _cmd, ...);
}

这个函数的主要作用是找到 IMP,执行 IMP。

是可以在 Objective-C 函数中直接调用 objc_msgSend ,来给一个对象发送消息

objc 复制代码
#import <objc/runtime.h>
#import <objc/message.h>

- (void)testObjcMsgSend {
    SEL sel = @selector(getString:); // 先获取方法SEL
    
    // 这样就可以成功执行方法,相当于[self addSubviewTemp:[UIView new] with:@"Temp"];
    // 在调用 objc_msgSend 时,要把它转换成对应的函数指针类型,所以前面加了一大串类型转换的代码
    // These functions must be cast to an appropriate function pointer type before being called.
    
    NSString *str = ((id (*)(id, SEL, NSString*))objc_msgSend)(self, sel, @"Temp");
    
    // 如果把 Xcode-> target -> build setting -> Enable Strict of objc_msgSend Calls 置为 NO,就可以用简单写法(无需对objc_msgSend 函数的类型进行转换)
    // NSString *str = objc_msgSend(self, sel);
    NSLog(@"执行了getString后 获得的值是%@", str);
}

- (NSString*)getString:(id)obj {
    return @"Eason";
}

2. 挑灯细览 - objc_msgSend 的实现

细看objc_msgSend 分析它的汇编实现,那就是四个步骤

1. 传入对象判空

2. 获取对象的 isa指针,找到对应的 Class 对象

对象有两种,普通对象和Tagged Pointer 类型的对象。

Tagged Pointer 类型的对象,是小对象,对象指针本身就存放了对象的值(比如 NSString, NSNumber)。这种对象的好处是可以节省内存空间 。但是也带来了新的问题,就是Tagged Pointer 类型的对象本身已经没有多的空间存放完整的 isa 指针信息了,所以只能有一个索引值。

索引值有啥用?

系统定义了两个数组,存放「Tagged Pointer 类型对象」的类信息,如下:

objc 复制代码
 extern "C" { 
    extern Class objc_debug_taggedpointer_classes[16*2];
    extern Class objc_debug_taggedpointer_ext_classes[256];
}

那索引值就可以用来在两个数组中找到对应的类信息了。

这是享元模式的应用,对于大量只有细微之处不一样的对象,就只创建一个对象存放基本不变的数据。对象们共享这个大对象,从而减少了内存的占用。

3. 在 Class 对象的缓存中找 IMP

哈希查找。

哈希桶在运行时会动态添加,在多线程环境下如何保证同步和数据安全呢?

写写冲突:引入全局互斥锁。

读写冲突:编译屏障。简单来说就是使用 ldp 汇编命令,把哈希桶中的数据 buckets 和 mask|occupied读到两个寄存器中,后续就用这两个寄存器进行哈希表查找。更新这两个寄存器的函数是 setBucketsAndMask,

cpp 复制代码
//设置更新缓存的哈希桶内存和mask值。
  void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
    // objc_msgSend uses mask and buckets with no locks.
    // It is safe for objc_msgSend to see new buckets but old mask.
    // (It will get a cache miss but not overrun the buckets' bounds).
    // It is unsafe for objc_msgSend to see old buckets and new mask.
    // Therefore we write new buckets, wait a lot, then write new mask.
    // objc_msgSend reads mask first, then buckets.

    //编译屏障
    // ensure other threads see buckets contents before buckets pointer
    mega_barrier();

    buckets = newBuckets;
    
    // ensure other threads see new buckets before new mask
    mega_barrier();
    
    mask = newMask;
    occupied = 0;
}

编译屏障保证了先执行 buckets 赋值而后执行 mask 赋值,从而使得多线程环境下读写也不会有数据一致性问题。

4. 找到了 IMP 就执行 IMP,没找到就执行objc_msgSend_uncached 函数(查找方法并执行)

这一步就是我们熟悉的、去 class 的 method_list 找,找不到就去父类中找。

然后动态方法决议。

然后消息转发。


参考文章:

  1. iOS 调用 IMP/objc_msgSend 详细说明: 讲了 objc_msgSend 的定义,调用方法;如何直接获取 IMP 并调用
  2. 深入解构 objc_msgSend 函数的实现:objc_msgSend 的汇编实现,以及用到的技术
  3. 从源代码看 ObjC 中消息的发送:演示了如何在查找方法实现时、替换缓存。
相关推荐
茶底世界之下2 小时前
类型擦除之后,Metal 滤镜组合为什么不能只执行一个 Filter?
ios·swift
传奇开心果编程4 小时前
【SwiftUI提高练中学】第8课 网络层架构:拦截器、重试、缓存与离线优先
学习·macos·ui·ios·swiftui
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
tink13 小时前
告别 Xcode IDE:用 VS Code + SweetPad + XcodeGen 开发 iOS 应用的完整指南
ios·swiftui·xcode
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
黑科技iOS上架16 小时前
iOS隐私合规扫描工具
ios·审核
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer