消息发送之快速查找(objc_msgSend)

clang源码

源码:
LGTeacher *teacher = [LGTeacher alloc];
[teacher sayHello];
转换之后:
LGTeacher *teacher = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)teacher, sel_registerName("sayHello"));
 

当我们对一个方法调用 进行 clang源码之后 发现 方法的调用 底层实现为 objc_msgSend 函数 即 消息发送

objc_msgSend(id receiver, SEL op, ...)

第一个参数id receiver为消息的接收者
第二个参数SEL op为消息的名称SEL
...为可变参数

objc_msgSend 汇编源码

通过源码进行搜查发现 objc_msgSend 函数

/********************************************************************
 *
 * 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
 *
 ********************************************************************/

为汇编实现 原因
1、 因objc_msgSend 为OC方法核心,调用极其频繁,出于性能考虑,这个函数内部是用汇编来实现。
2、方法参数的动态性,汇编调用函数时传递的参数是不确定的,那么发送消息时,直接调用一个函数就可以发送所有的消息。

源码分析

LGetIsaDone


    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

///判断 receiver是否存才?p0 第一个参数
    cmp p0, #0  // nil check and tagged pointer check
/// 是否支持 taggedPointers对象
#if SUPPORT_TAGGED_POINTERS
/// 是否为空或者小对象类型 (为空 返回 不为空 获取小对象的isa)
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
/// 不是小对象类型
#else
/// 直接返回
    b.eq    LReturnZero
#endif
/// receiver存才 从寄存器x0中获取 isa存入 p13
    ldr p13, [x0]       // p13 = isa
/// 获取 Class
    GetClassFromIsa_p16 p13     // p16 = class
/// 获取 isa完毕
LGetIsaDone:
/// 开启缓存查找流程即快速查找流程
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
///  为nil 直接返回
    b.eq    LReturnZero     // nil check  直接返回
/// 小对象类型 获取 isa
    // 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

首先判断objc_msgSend第一个参数(id receiver为消息的接收者)是否存再,不存在判断是否是支持小对象类型(taggedPointers)的参数 是获取小对象isa 不是 返回空
receiver存在 获取 消息接收者的isa 根据isa获取 class 可能 类对象 可能是元类

CacheLookup NORMAL, _objc_msgSend


/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP <function>
 *
 * Locate the implementation for a selector in a class method cache.
 *
 * When this is used in a function that doesn't hold the runtime lock,
 * this represents the critical section that may access dead memory.
 * If the kernel causes one of these functions to go down the recovery
 * path, we pretend the lookup failed by jumping the JumpMiss branch.
 *
 * Takes:
 *   x1 = selector
 *   x16 = class to be searched
 *
 * Kills:
 *   x9,x10,x11,x12, x17
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *          (not found) jumps to LCacheMiss
 *
 ********************************************************************/

 .... 有省略 
LLookupStart$1:
///  isa首地址 平移16字节 获取 cache_t  cache = 高16位 msak + 低48位buckets
    // p1 = SEL, p16 = isa首地址
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
/// arm 64
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
/// 通过chche&掩码将高位mask抹零、得到buckets
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
/// 通过掩码mask 获取 哈希下标
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
    and p12, p1, p11                // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif

/// p12是获取到的下标,然后逻辑左移4位,再由p10(buckets)平移,得到对应的bucket保存到p12中
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
/// 将p12属性imp 和 sel分别赋值为p17 和 p9
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
/// 比较 bucket的 sel 是否等于 传入的 sel
1:  cmp p9, p1          // if (bucket->sel != _cmd)
/// 不等于 跳到 2
    b.ne    2f//     scan more
/// 如果相同 直接返回 imp 找到了
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
/// 判断当前bucket的sel 是否为0 为0 往下走 不为 0     (cbz    p9, __objc_msgSend_uncached) 开始慢速查找流程
    CheckMiss $0            // miss if bucket->sel == 0
/// 判断 当前的bucket 是否 为 buckets的第一个元素
    cmp p12, p10        // wrap if bucket == buckets
/// 是的话 直接 跳 3
    b.eq    3f
/// 不相等,以x12即当前bucket地址往前移动一个bucket大小为地址读取其中的值,并赋值为p17和p9,同时x12=x12-BUCKET_SIZE
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
/// 跳到 1 循环
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    
///  接上步骤,如果p12和buckets数组第一个相等,需要移动到buckets数组的最后一位
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.
/// 通过bucket结构体得到imp-sel   将p12属性imp 和 sel分别赋值为p17 和 p9
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
/// 比较 bucket的 sel 是否等于 传入的 sel
1:  cmp p9, p1          // if (bucket->sel != _cmd)
/// 不等于 跳2
    b.ne    2f          //     scan more
/// 等于 命中 直接返回 imp
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
/// 判断当前bucket的sel 是否为0 为0 往下走 不为 0     (cbz    p9, __objc_msgSend_uncached) 开始慢速查找流程
    CheckMiss $0            // miss if bucket->sel == 0
/// 判断 当前的bucket 是否 为 buckets的第一个元素
    cmp p12, p10        // wrap if bucket == buckets
/// 是 跳 3
    b.eq    3f
/// 从最后一个元素往前查找
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
/// 跳出 去往慢速查询流程    b    __objc_msgSend_uncached 
    JumpMiss $0

.endmacro

通过 平移 获取 cache 匹配传进来的 sel 找到 返回imp 没有 进入慢速流程

流程图

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