前言
前面探究了类里面的重要的变量,iOS底层原理之cache_t分析了缓存方法调用流程。追根溯源找到了objc_msgSend
准备工作
Runtime
Runtime通常叫它运行时,还有一个大家常说的编译时,它们之间的区别是什么
编译时:顾名思义正在编译的时候,啥叫编译呢?就是编译器把源代码翻译成机器能够识别的代码。编译时会进行词法分析,语法分析主要是检查代码是否符合苹果的规范,这个检查的过程通常叫做静态类型检查运行时:代码跑起来,被装装载到内存中。运行时检查错误和编译时检查错误不一样,不是简单的代码扫描,而是在内存中做操作和判断
Runtime有两个版本,一个Legacy版本(早期版本),一个Modern版本(现行版本)
- 早期版本对应的编程接口:Objective-C 1.0
- 现行版本对应的编程接口:Objective-C 2.0,源码中经常看到的OBJC2
- 早期版本用于Objective-C 1.0,32位的Mac OS X的平台
- 现行版本用于Objective-C 2.0,iPhone程序和Mac OS X v10.5及以后的系统中的64位程序
Runtime调用三种方式
- Objective-C方式,[penson sayHello]
- Framework & Serivce方式,isKindOfClass
- Runtime API方式,class_getInstanceSize
方法的本质
所有的方法调用都是通过objc_msgSend发送的,所以方法的本质就是消息发送,既然方法调用都是通过objc_msgSend的,那么可不可以直接通过objc_msgSend发消息呢?
int main(int argc, char * argv[]) {
@autoreleasepool {
LGPerson *perosn = [LGPerson alloc];
[perosn sayHello];
//objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSend((id)perosn, sel_registerName("sayHello"));
}
return 0;
}
2021-07-12 18:14:22.659269+0800 objc_msgSend[5461:254082] sayHello
2021-07-12 18:14:22.659722+0800 objc_msgSend[5461:254082] sayHello
通过objc_msgSend和[perosn sayHello]结果是一样的,同时也验证了方法的本质是消息发送。在用objc_msgSend方式发送消息。验证过程需要注意两点
- 必须导入相应的头文件
#import <objc/message.h> - 关闭objc_msgSend检查机制:
target --> Build Setting -->搜索objc_msgSend -- Enable strict checking of obc_msgSend calls设置为NO
objc_msgSend汇编探究
_objc_msgSend
ENTRY _objc_msgSend // 汇编常用方法进入ENTRY
UNWIND _objc_msgSend, NoFrame // 常用语法
cmp p0, #0 // 寄存器p0是objc_msgSend的第一个参数`id self`,和0比较判断有没有消息接收者
#if SUPPORT_TAGGED_POINTERS // 判断条件是否支持 tagged pointer
b.le LNilOrTagged // 执行LNilOrTagged开始向下执行
#else
b.eq LReturnZero // p0 = 0,没有消息接收者,消息为空
#endif
ldr p13, [x0] // x0寄存器为消息接收者,将x0赋值给p13,p13 = 接收者的isa
GetClassFromIsa_p16 p13, 1, x0 // GetClassFromIsa_p16的解释在下方,最终结果 p16 =接收者的class
LGetIsaDone: // 指令完成 其实是一个通过receiver找class的过程,因为class中有cache
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero: // return nil
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend // _objc_msgSend结束
GetClassFromIsa_p16获取Class
// GetClassFromIsa_p16 p13, 1, x0; src=p13=isa needs_auth=1 auth_address=x0
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA // 不满足直接看else
// Indexed isa
mov p16, \src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__ // linux MacOS X的系统 此处看else分支arm64系统
.if \needs_auth == 0 // needs_auth=1 不满足 _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address // 操作结束之后,p16是接收者的class
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
ExtractISA
// ExtractISA p16, \src, \auth_address
.macro ExtractISA
and $0, $1, #ISA_MASK // $0=p16,$1=src=isa,解释:$1与ISA_MASK进行与操作得到的class,存到$0=p16
.endmacro