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的伪代码如下:
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 ,来给一个目标发送消息
#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 类型目标」的类信息,如下:
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,
//设置更新缓存的哈希桶内存和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 找,找不到就去父类中找。
然后动态办法决议。
然后消息转发。
参阅文章:
- iOS 调用 IMP/objc_msgSend 详细说明: 讲了 objc_msgSend 的界说,调用办法;如何直接获取 IMP 并调用
- 深入解构 objc_msgSend 函数的完成:objc_msgSend 的汇编完成,以及用到的技术
- 从源代码看 ObjC 中消息的发送:演示了如安在查找办法完成时、替换缓存。