Runtime学习之objc_msgSend分析

我们在上一个篇章cahce的结构分析中,对cache的整个闭环流程进行了探索,最终停留在了_objc_msgSend,那么想要继续探索就得对Runtime的知识要有一定的了解了,Runtime官方文档

一.Runtime运行时简介

1.概念

  • 编译时 :顾名思义就是正在编译的时候 . 那啥叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码 (当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言)

    编译时就是简单的作一些翻译工作 :比如检查 兄die 你有没有粗心写错啥关键字了啊,有啥词法分析,语法分析之类的过程。就像个老师检查学生的作文中有没有错别字和病句一样 。如果发现啥错误编译器就告诉你,如果你用微软的VS的话,点下build,那就开始编译,如果下面有errors或者warning信息,那都是编译器检查出来的,所谓这时的错误就叫编译时错误,这个过程中做的啥类型检查也就叫编译时类型检查,或静态类型检查(所谓静态嘛就是没把真把代码放内存中运行起来,而只是把代码当作文本来扫描下)。所以有时一些人说编译时还分配内存啥的肯定是错误的说法

  • 运行时:就是代码跑起来了,被装载到内存中去了,(你的代码保存在磁盘上没装入内存之前是个死家伙。只有跑到内 存中才变成活的)。而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样,不是简单的扫描代码,而是在内存中做些操作,做些判断

  • runtime 其实c/c++/汇编 混编写出的一套API

2.版本信息

Runtime有两个版本 一个Legacy版本(早期版本) ,一个Modern版本(现行版本)

  • Legacy版本,如果你改变类中的实例变量的布局,必须从它继承的类重新编译。

  • Modern版本,如果你改变类中的实例变量的布局, 不必从它继承的类重新编译。

  • 早期版本对应的编程接口:Objective-C 1.0

  • 现行版本对应的编程接口:Objective-C 2.0

  • 早期版本用于Objective-C 1.0, 32位的MacOSX的平台上

  • 现行版本:iPhone程序和Mac OS X v10.5及以后的系统中的 64 位程序

3.交互方式

Objective-C程序有三种途径和运行时系统交互:

  • 1.通过Objective-C代码,比如:[person sayNB]。
  • 2.通过Framework&Serivce的方法,比如:isKindofClass。
  • 3.通过Runtime提供的API接口,比如:class_getInstanceSize。

二.引入objc_msgSend

1.测试

接下来我们来做一下测试,代码如下

#import <objc/message.h> 

LGPerson *person = [LGPerson alloc];
[person sayNB];
// 子类是不是没有 -> 父类
[person sayHello];

2.clang编译

通过clang编译main.m文件生成.cpp文件,查看相关代码如下

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"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_c501bc_mi_2);
    }
    return 0;
}

可以看到编译之后会发现Objective-C方法的调用,最终在底层是通过objc_msgSend进行消息发送实现

  • 调用方法 = 消息发送 : objc_msgSend(消息的接受者,消息的主体(sel + 参数))

3.objc_msgSend

我们也直接使用objc_msgSend去调用

前提:在使用时一定要检查机制选项,如图

在这里插入图片描述
执行代码如下

objc_msgSend(person, @selector(sayNB));

输出结果
2021-06-29 15:59:32.669926+0800 001-运行时感受[27390:315754] 666

4.objc_msgSendSuper

在第一步中调用的方法sayHello,其实是调用得父类的方法,继续调试就发现了objc_msgSendSuper,那么我们去源码中看下objc_msgSend,objc_msgSendSuper函数信息

objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

5.objc_super源码

从上面函数的参数可以看到,需要传入objc_super,那么继续查看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 */
    //父类第一个搜索
};

6.仿写源码

我们也可以参照源码,去尝试调用,结果一样实现了方法调用

//参照源码写法
struct objc_super kc_objc_super;
//让person为消息接受者
kc_objc_super.receiver = person;
//我们也可以改变第一个要搜索的类,只不过默认是父类
kc_objc_super.super_class = LGPerson.class;

objc_msgSendSuper(&kc_objc_super,@selector(sayHello));

其实通过源码的描述,以及测试可以得出结论:

  • 默认情况父类作为第一接受者,如果父类处理不了的消息会转发给receiver
  • 方法的本质:消息接收者通过sel查找imp的过程。

三.objc_msgSend源码分析

1.查找objc_msgSend

首先还是全局搜索objc_msgSend,查找arm64架构的源码

在这里插入图片描述
进入之后找到objc_msgSend相关代码

	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
	//判断p0(消息接受者)是不是为0(也就是空) 
	cmp	p0, #0			// nil check and tagged pointer check
	//判断SUPPORT_TAGGED_POINTERS 决定是走LNilOrTagged 还是LReturnZero,但是你会发现不管走那个最终都会通过LReturnZero中的END_ENTRY _objc_msgSend又返回_objc_msgSend
	
#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
//说白了,上面的操作就是为取isa,[x0]就是开始存放的p0消息接受者
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
//到这里就是通过消息接受者receiver取需找class
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#endif

LReturnZeroLNilOrTagged相关汇编

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
	b.eq	LReturnZero		// nil check
	GetTaggedClass
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret

	END_ENTRY _objc_msgSend

GetClassFromIsa_p16 相关汇编

//.macro 表示宏定义
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
	// 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
	//说白了就是class 存在p16  旁边有注释
1:
//and $0,$1,#ISA_MASK
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro

可以看到objc_msgSend得源码,其实是汇编写的,那么问题1来了为什么使用汇编写呢?其次通过上面的部分汇编可以看出就是通过消息接受者receiver去寻找class,那么寻找class得目的想必通过前面的学习,肯定是为了获取cache里面的方法缓存,那么是如何执行的呢

预知后续详情,请听下回方法快速与慢速查找分解

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值