objc_msgSend 流程

1.runtime简介

Runtime 分为两个版本,legacy 和 modern,分别对应 Objective-C 1.0 和 Objective-C 2.0。我们通常只需要专注于modern

通过c、c++、混编给我们的object-c提供运行时功能

调用runtime有三种途径

(1)Objective-C Code

          例:[person sayNB] 

(2)Framework&Serivce

          例: isKindofClass

(3)Runtime API 

          例:class_getInstanceSize

2.探索方法本质

1.clang


通过clang -rewrite-objc main.m -o main.cpp将main.m编译成 c++文件


我们发现都是通过底层函数来发送消息的 ,当然我们也可以尝试直接调用底层方法。

注意:如果要在工程当中直接使用 objc_msgSend API,我们需要导入头文件 <objc/message.h> 和 将 Enbale Strict Checking of objc_msgSend Calls 设置为 NO,这样才不会报错。

看下打印结果


2.发送消息的两种方式

objc_msgSend  上面已经用过了

 objc_msgSendSuper 调用之前 我们在建一个类


如果我们用[person sayHello]也就是说消息的接受者还是自己 但是方法在父类 这时候就用到objc_msgSendSuper


打印结果 正常

主要也就是研究sel->imp

3.探索 objc_msgSend流程

objc_msgSend() 是用 汇编语言 实现的。之所以使用汇编实现,一方面消息发送的过程需要足够的快速,高级语言在执行的时候都是需要翻译成汇编语言,经过编译成被机器识别的二进制文件,使用汇编可以省去这一翻译过程,可以更快速被机器识别;且对于消息的发送,存在很多未知的参数,这有很多不确定性,使用汇编的寄存器要比 C 或者 C++ 表现好的多。

源码查看:

/********************************************************************

 *

 * id objc_msgSend(id self, SEL _cmd, ...);

 * IMP objc_msgLookup(id self, SEL _cmd, ...);

 * 

 * objc_msgLookup ABI:

 * IMP returned in x17

 * x16 reserved for our use but not used

 *

 ********************************************************************/

#if SUPPORT_TAGGED_POINTERS

.data

.align3

.globl _objc_debug_taggedpointer_classes

_objc_debug_taggedpointer_classes:

.fill16,8,0

.globl _objc_debug_taggedpointer_ext_classes

_objc_debug_taggedpointer_ext_classes:

.fill256,8,0

#endif

ENTRY _objc_msgSend

UNWIND _objc_msgSend, NoFrame

cmp p0,#0 // nil check and tagged pointer check

#if SUPPORT_TAGGED_POINTERS

b.le LNilOrTagged //  (MSB tagged pointer looks negative)

#else

b.eq LReturnZero

#endif

ldr p13, [x0] // p13 = isa

GetClassFromIsa_p16 p13 // p16 = class

LGetIsaDone:

// calls imp or objc_msgSend_uncached

CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS

LNilOrTagged:

b.eq LReturnZero // nil check

// tagged

adrp x10, _objc_debug_taggedpointer_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF

ubfx x11, x0,#60, #4

ldr x16, [x10, x11, LSL#3]

adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE

add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF

cmp x10, x16

b.ne LGetIsaDone

// ext tagged

adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF

ubfx x11, x0,#52, #8

ldr x16, [x10, x11, LSL#3]

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

ENTRY _objc_msgLookup

UNWIND _objc_msgLookup, NoFrame

cmp p0,#0 // nil check and tagged pointer check

#if SUPPORT_TAGGED_POINTERS

b.le LLookup_NilOrTagged //  (MSB tagged pointer looks negative)

#else

b.eq LLookup_Nil

#endif

ldr p13, [x0] // p13 = isa

GetClassFromIsa_p16 p13 // p16 = class

LLookup_GetIsaDone:

// returns imp

CacheLookup LOOKUP, _objc_msgLookup

#if SUPPORT_TAGGED_POINTERS

LLookup_NilOrTagged:

b.eq LLookup_Nil // nil check

// tagged

adrp x10, _objc_debug_taggedpointer_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF

ubfx x11, x0,#60, #4

ldr x16, [x10, x11, LSL#3]

adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE

add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF

cmp x10, x16

b.ne LLookup_GetIsaDone

LLookup_ExtTag:

adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF

ubfx x11, x0,#52, #8

ldr x16, [x10, x11, LSL#3]

b LLookup_GetIsaDone

// SUPPORT_TAGGED_POINTERS

#endif

LLookup_Nil:

adrp x17, __objc_msgNil@PAGE

add x17, x17, __objc_msgNil@PAGEOFF

ret

END_ENTRY _objc_msgLookup

STATIC_ENTRY __objc_msgNil

// x0 is already zero

mov x1,#0

movi d0,#0

movi d1,#0

movi d2,#0

movi d3,#0

ret

END_ENTRY __objc_msgNil

ENTRY _objc_msgSendSuper

UNWIND _objc_msgSendSuper, NoFrame

ldp p0, p16, [x0] // p0 = real receiver, p16 = class

// calls imp or objc_msgSend_uncached

CacheLookup NORMAL, _objc_msgSendSuper

END_ENTRY _objc_msgSendSuper

// no _objc_msgLookupSuper

ENTRY _objc_msgSendSuper2

UNWIND _objc_msgSendSuper2, NoFrame

ldp p0, p16, [x0] // p0 = real receiver, p16 = class

ldr p16, [x16,#SUPERCLASS] // p16 = class->superclass

CacheLookup NORMAL, _objc_msgSendSuper2

END_ENTRY _objc_msgSendSuper2

ENTRY _objc_msgLookupSuper2

UNWIND _objc_msgLookupSuper2, NoFrame

ldp p0, p16, [x0] // p0 = real receiver, p16 = class

ldr p16, [x16,#SUPERCLASS] // p16 = class->superclass

CacheLookup LOOKUP, _objc_msgLookupSuper2

END_ENTRY _objc_msgLookupSuper2

.macro MethodTableLookup

// push frame

SignLR

stp fp, lr, [sp,#-16]!

mov fp, sp

// save parameter registers: x0..x8, q0..q7

sub sp, sp,#(10*8 + 8*16)

stp q0, q1, [sp,#(0*16)]

stp q2, q3, [sp,#(2*16)]

stp q4, q5, [sp,#(4*16)]

stp q6, q7, [sp,#(6*16)]

stp x0, x1, [sp,#(8*16+0*8)]

stp x2, x3, [sp,#(8*16+2*8)]

stp x4, x5, [sp,#(8*16+4*8)]

stp x6, x7, [sp,#(8*16+6*8)]

str x8,    [sp,#(8*16+8*8)]

// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)

// receiver and selector already in x0 and x1

mov x2, x16

mov x3,#3

bl _lookUpImpOrForward

// IMP in x0

mov x17, x0

// restore registers and return

ldp q0, q1, [sp,#(0*16)]

ldp q2, q3, [sp,#(2*16)]

ldp q4, q5, [sp,#(4*16)]

ldp q6, q7, [sp,#(6*16)]

ldp x0, x1, [sp,#(8*16+0*8)]

ldp x2, x3, [sp,#(8*16+2*8)]

ldp x4, x5, [sp,#(8*16+4*8)]

ldp x6, x7, [sp,#(8*16+6*8)]

ldr x8,    [sp,#(8*16+8*8)]

mov sp, fp

ldp fp, lr, [sp],#16

AuthenticateLR

.endmacro

STATIC_ENTRY __objc_msgSend_uncached

UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION

// Out-of-band p16 is the class to search

MethodTableLookup

TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

STATIC_ENTRY __objc_msgLookup_uncached

UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION

// Out-of-band p16 is the class to search

MethodTableLookup

ret

END_ENTRY __objc_msgLookup_uncached

STATIC_ENTRY _cache_getImp

GetClassFromIsa_p16 p0

CacheLookup GETIMP, _cache_getImp

LGetImpMiss:

mov p0,#0

ret

END_ENTRY _cache_getImp

源码分析:

首先 ENTRY 进入  _objc_msgSend:

cmp 是比较函数, p0 是  _objc_msgSend 传入的第一个参数,也就是要响应方法的实例,一般称之为 “消息接收者” ,#0 代表 值 0,这一步就是判断 p0 是否为空。如果支持taggedpointer类型,进入 LNilOrTagged 否则进入 LReturnZero ,结束  _objc_msgSend 流程 。开始 一轮 向 nil 发送消息的流程。如果 p0 不为空,向下执行


读取 x0 的首地址 存入 p13。x0 为第一个参数,依然是 消息接收者,不管它是类还是对象,它的第一个成员都是 isa, 所以取x0的首地址,即为 isa, 将 isa 存入 p13 。这里取 isa 的目的是因为 ,不管是对象方法还是类方法,我们都可以通过 isa 的指向 在类或元类的缓存或方法列表中去查找。所以接下来就要通过 isa 取到类或元类。


GetClassFromIsa_p16就是获取类或元类的过程


CacheLookup  从缓存获取imp流程

先看下CacheLookup源码:

首先在类的结构分析中,我们已知 类的结构排布 为 isa、superclass、cache_t、class_data_bits,这里我们要进行的是查找缓存的流程,缓存的信息是存储在 cache_t 中的。


x16 是我们上一步中获取到的 类信息,x16 偏移 16字节 就是取到 cache_t 结构,存入 p11 中

在 arm64架构 cache_t 的第一个成员为_maskAndBuckets ,所以这里的 p11 存储的就是 _maskAndBuckets 。

p11 为 _maskAndBuckets ,它的低48位存储 buckets , 高16位存储 mask,


将 p11 & #0x0000ffffffffffff,就是取出  _maskAndBuckets 中 的第0 - 47 字节,得到的就是 buckets 赋值给 p10 。

之后,将 p11 逻辑右移 48个字节,相当于只保留 mask 的值,与 p1(sel) 进行与运算之后,得到 sel & mask 存入 p12。这里的本质就是我们在写入内存遇到的 `cache_hash` 方法一模一样,目的就是拿到方法缓存的哈希下标。 在 cache_t 的结构中,mask 为 缓存表 总长度的值 - 1,这里的 sel & mask 得到的值 必定落在 buckets缓存表的长度 范围内,作为开始遍历查找缓存的 下标值。


获取完毕buckets、 mask、 _cmd & mask 的值之后,做了这样一个操作:p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) 。PTRSHIFT 的值在arm64 架构下的值为3 ,所以((_cmd & mask) << (1+PTRSHIFT))得到的结果为16的倍数,具体的倍数取决于 _cmd & mask 计算出的大小。buckets 中存储的是 sel 和 imp ,每一个 bucket 占用16字节,_cmd & mask 作为 buckets 中的初始遍历地址,这一步的操作 其实就是将 buckets 偏移到 初始查询位置,已取到初始查询的 bucket 存入 p12 中。 取到了 初始查询 bucket, 也就取到了 bucket 中的 sel 和 imp ,分别存入 p9 和 p17。 就是内存偏移取对应位置的bucket 把其中的imp和sel分别存入


接下来比较上一步获取到的 sel(p9 )是否与我们要查找的 sel (p1) 相同,如果相同 通过 CacheHit 将 imp 返回,如果不匹配,跳转到 2:

如果上面的结果不匹配,这里将 把比对的样本 进行更换 ,也就是进入循环,更换比较的bucket。p10 为 buckets散列表的首地址,p12 为当前对比的 bucket ,如果 p10 与 p12 相等,则意味着当前遍历到了 buckets 的首地址,此时执行3:

p12 = buckets + (mask << 1+PTRSHIFT) 和上面的指针偏移原理是一样的,只不过这次偏移的位置不同,因为mask 是总长度的值-1,所以此次操作是偏移到已存储的 buckets 中的最后一个bucket。

将当前遍历的位置,移到最后,因为遍历是从_cmd & mask的位置开始,通过 * --bucket 向前遍历,当到达第一个时,则将位置移到最后,继续向前遍历,以确保整个缓存表全部查找。

通过 将 buckets 的指针 偏移 获取到新的 bucket ,取到sel ,继续回到1:执行对比操作,命中则 CacheHit ,否则继续执行 2: 如果整个 buckets 遍历结束,依然没有得到匹配信息,则跳转到 CheckMiss


在 NORMAL 模式下,会进入 __objc_msgSend_uncached 流程。


__objc_msgSend_uncached 中最核心的逻辑就是 MethodTableLookup ,因为我们的缓存并没有命中,这里是开始去方法列表 (methodList) 的查找流程。


我们观察 MethodTableLookup 内容之后会定位到 _lookUpImpOrForward。真正的方法查找流程核心逻辑是位于 _lookUpImpOrForward 里面的。 至此,objc_msgSend() 的快速查找流程就结束了,接下来进入的 慢速查找流程,也就是通过 isa 与 superclass 的指向,一层层寻找下去,直到整个 objc_msgSend() 发送完毕。

下篇文章继续讲解快速查找和消息转发~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354