您的位置:首页 > 移动开发 > IOS开发

iOS OC 方法的本质

2020-04-20 15:01 791 查看

iOS OC 方法的本质

前言:

前面探究了方法在类中的缓存,那么方法的本质是什么呢?方法调用在底层做了什么呢?今天我们来探索一下:

1. 方法本质初探

看一下一段代码:
先定义一个

LGPerson
类,然后定义
sayNB
对象方法,然后在
main
函数中调用

int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person sayNB];
}
return 0;
}

然后通过

clang
生成
cpp
文件,在底层编译的
cpp
文件中查看
main
函数如下:

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));imp -  函数
}
return 0;
}

由此:我们可以简单得出,

方法
的本质是通过
objc_msgSend
发送消息,第一个参数为
id
消息接受者,第二个参数为
sel
方法编号。

那么我们定义的函数会调用

objc_msgSend
发送消息吗?
我们定义下面函数,并在
main
中调用,

void run(){
NSLog(@"%s",__func__);
}

通过

clang
查看
cpp
文件,发现函数不需要调用
objc_msgSend
,函数可以直接通过
函数名
(指针),找到函数的实现,不需要像
方法
通过
sel
,找到
ipm
,再找到方法的实现。

父类
发送消息(对象方法):

struct objc_super lgSuper;
lgSuper.receiver = s;
lgSuper.super_class = [LGPerson class];
objc_msgSendSuper(&lgSuper, @selector(sayHello));

父类
发送消息(类方法):

struct objc_super myClassSuper;
myClassSuper.receiver = [s class];
myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));// 元类
objc_msgSendSuper(&myClassSuper, sel_registerName("sayNB"));

objc_super
源码:

struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus)  &&  !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif

因此,在调用

Runtim api
父类
发送消息时,需要设置
receiver
super_class

问题:在测试中,不要严格识别参数,需要如下设置:

2.
objc_msgSend
汇编分析

objc
源码中断点

然后
Debug -> Debug Workflow ->always Show Disassembly
进行汇编分析:

objc_msgSend
处断点,

通过

control + in
,查看
libobjc.A.dylib objc_msgSend
,发现
objc_msgSend
底层是用
汇编
实现的。

补充:

为什么`objc_msgSend`用汇编实现呢?
1. 在性能方面,`汇编`更容易被机器识别
2. 在发送消息时,有很多未知的参数,c 语言中不能通过写一个函数来保留未知的参数并且跳转到一个任意的
函数指针,c语言没有满足做这件事的必要特性。

汇编寄存器
arm64下有31位通用寄存器,x0 - x7,是参数,返回值会放到 x0中

objc_msgSend
的汇编分析:

首先,在

objc
源码中全局搜索
objc_msgSend
找到汇编源码,


汇编代码:

具体分析:

1. cmp	p0, #0			// nil check and tagged pointer check
先对比当前0号寄存器是否为空,为空,当前没有接收者

2. 判断 SUPPORT_TAGGED_POINTERS
直接执行 LNilOrTagged 或者 LReturnZero
3. 当有消息接收者,正常情况下,拿到 p13	// p13 = isa
4. GetClassFromIsa_p16 p13	 通过p13(isa),获取 Class ;
GetClassFromIsa_p16 先平移,取值 shiftcls,然后得到 Class, 或者 isa & Mask 直接获取 Class

LGetIsaDone
源码:

5. LGetIsaDone 查找isa完毕  开始正常查找 CacheLookup NORMAL
5.1  ldp	p10, p11, [x16, #CACHE]	// p10 = buckets, p11 = occupied|mask
先平移16字节,获得cache,找到缓存方法的 buckets 和 occupied
5.2 and	w12, w1, w11		// x12 = _cmd & mask
通过 _cmd & mask 获取哈希的下标,
5.3 循环查找 bucket  add	p12, p10, p12, LSL
5.4 ldp p17, p9, [x12] 通过 sel 找到 bucket 中的cmd 对比,相等直接返回 CacheHit $0, 找不到,直接走 b.ne	2f 即:CheckMiss
cmp	p9, p1			// if (bucket->sel != _cmd)
6. CheckMiss 中找到后,b.eq 3f,进入步骤三,平移哈希,将方法缓存到 bucket中一份,
如果没有找到则 {imp, sel} = *--bucket,循环递归查找。
然后会在查找一遍 5.4 流程(
防止多线程,缓存更新),找不到缓存,则 JumpMiss $0

CheckMiss
代码:


当时

NORMAL
形式时,进入
__objc_msgSend_uncached
,如下:

MethodTableLookup
源码:

_class_lookupMethodAndLoadCache3
方法:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
问题:
为什么从汇编调用 C 方法?
_class_lookupMethodAndLoadCache3 是一系列慢速方法查找,没有必要使用汇编

总结:

1. ENTRY _objc_msgSend 进入
2. TAGGED_POINTERS  判断
3. GetClassFromIsa_p16 p13 通过 isa 获取 Class
4. 缓存查找 CacheLookup
5. cache_t 处理,处理哈希,查找 buckrt,找到返回{imp,sel} = *buckrt->imp,找不到 JumpMiss
6. 缓存中找不到方法 进入 __objc_msgSend_uncached
7. STATIC_ENTRY __objc_msgSend_uncached
8. MethodTableLookup  调用__class_lookupMethodAndLoadCache3

最后一个遗留问题,调用

_class_lookupMethodAndLoadCache3
中是怎么慢速查找的呢?下一篇接着探索。

  • 点赞 1
  • 收藏
  • 分享
  • 文章举报
亮亮不想说话 发布了18 篇原创文章 · 获赞 10 · 访问量 266 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: