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
- 收藏
- 分享
- 文章举报
相关文章推荐
- 联合体(union)的使用方法及其本质
- 24_方法_方法的本质_形参_实参_return语句
- SOA的本质是组织设计的一个模式一个方法
- 黑马程序员_iOS开发之OC之面向对象之id语法、构造方法、@category分类、类本质、description方法和SEL数据类型
- java学习之旅24--方法_方法的本质_形参_实参_return语句
- Atitit paip.对象方法的实现原理与本质.txt
- 再谈多线程信号量同步(协调)方法的本质
- OC中类的Self 和 Super调用方法的本质
- Objective-c - 继承的本质, new方法
- 黑马程序员14——用IL查看:属性(properties)的本质是方法
- 阅读软件开发本质和方法的收获
- C# 匿名方法和拉姆达表达式 (2012-04-27 23:27:15)转载▼ 标签: 杂谈 匿名方法本质上是一传递给委托的代码块,是使用委托的另一种方法。 规则: 1、匿名方法中不能使用跳转语句跳
- 联合体(union)的使用方法及其本质
- 联合体(union)的使用方法及其本质
- [C] 联合体(union)的使用方法及其本质
- 黑马程序员——OC学习之类的本质和常用的继承自NSObject的方法
- 联合体(union)的使用方法及其本质
- Java方法的本质、形式参数、返回值
- CString的GetBuffer用法,GetBuffer本质,GetBuffer常见问题解决方法
- 应用程序架构本质,第 3 部分: 软件开发方法学入门