IOS底层原理之Runimte 运行时&方法的本质

385 阅读3分钟

前言

前面探究了类里面的重要的变量,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

缓存查询流程图

cache遍历.jpg