objc_msgSend流程分析

Runtime.png

1、Runtime

1.1、Runtime :Objective-C运行时,指代码跑起来了.被装载到内存中去的过程,具有动态性,能够进行消息传递动态方法解析消息转发类型编码声明属性等一系类操作。

1.2、Runtime有两个版本:一个现在(modern)版本,一个传统(legacy)版本
传统版本对应的编程接⼝:Objective-C 1.0。
现⾏版本对应的编程接⼝:Objective-C 2.0 。
传统版本⽤于Objective-C 1.0, 32位Mac OS X的平台上。
现⾏版本:iPhone程序Mac OS X v10.5 及以后的系统中的 64 位程序 。

1.3、Runtime交互
Objective-C程序可在三个不同
的层次上与Runtime进行交互:通过Objective-C源代码Foundation框架的NSObject类中定义的方法直接调用运行时函数

Runtime交互结构.png

Runtime交互三种路径.png

2、objc_msgSend

解释:发送一个带有简单返回值的消息到一个类的实例

//发送一个带有简单返回值的消息到一个类的实例。
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

objc_msgSend:
self-->self指向接收消息的类实例的指针
op-->处理消息的方法的选择器
...-->方法参数的变量参数列表

//发送一个带有简单返回值的消息到类的实例的超类。
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

objc_msgSendSuper:
super-->指向一个objc_super数据结构的指针。传递标识 消息被发送到的上下文,包括接收消息的类的实例
消息和开始搜索方法实现的超类。

op-->SEL类型的指针。传递将处理消息的方法的选择器。
...-->方法参数的变量参数列表

接下来通过源码来查看:

  //main函数中方法的调用
 #import <Foundation/Foundation.h>
 #import <objc/message.h>

@interface LGTeacher : NSObject
- (void)sayHello;
@end

@implementation LGTeacher
- (void)sayHello{
    NSLog(@"666 %s",__func__);
}
@end

@interface LGPerson : LGTeacher
- (void)sayHello;
- (void)sayNB;
@end

@implementation LGPerson
- (void)sayNB{
    NSLog(@"666");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *person = [LGPerson alloc];
        LGTeacher *teacher = [LGTeacher alloc];
        [person sayNB];
        [person sayHello];
    }
    return 0;
}

通过Clang查看源码:

__OBJC_RW_DLLIMPORT void objc_msgSend(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void);
__OBJC_RW_DLLIMPORT void objc_msgSend_stret(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper_stret(void);

LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
LGTeacher *teacher = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), 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"));

当遇到方法调用时,编译器生成对对应
objc_msgSend 、 objc_msgSend_stret, objc_msgSendSuper、 objc_msgSendSuper_stret。
其中
发送到超类的消息使用objc_msgSendSuper
其他消息发送使用 objc_msgSend
将数据结构作为返回值的方法
objc_msgSendSuper_stret
objc_msgSend_stret

在main方法中调用objc_msgSend ,objc_msgSendSuper,此时发现使用clang会报错,无法生成main.cpp文件

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *person = [LGPerson alloc];
        LGTeacher *teacher = [LGTeacher alloc];
        [person sayNB];
        [person sayHello];
        //2.消息发送
     objc_msgSend(person, sel_registerName("sayNB"));
        
       struct objc_super lgsuper;
    lgsuper.receiver = person;//消息的接收者还是person
    lgsuper.super_class = [LGTeacher class];//告诉父类是谁
     objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"));

    }
    return 0;
}

objc_super结构
```#ifndef OBJC_SUPER
#define OBJC_SUPER

/// Specifies the superclass of an instance. 
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 */
};
#endif

receiver:消息的接收者
super_class:指定消息的接收者超类

输出打印:

2021-06-27 16:36:12.851501+0800 001-运行时感受[8467:207092] 666
2021-06-27 16:36:12.851894+0800 001-运行时感受[8467:207092] 666 -[LGTeacher sayHello]
2021-06-27 16:36:12.851951+0800 001-运行时感受[8467:207092] 666
2021-06-27 16:36:12.851992+0800 001-运行时感受[8467:207092] 666 -[LGTeacher sayHello]

总结:
[person sayNB]等价于objc_msgSend(person, sel_registerName("sayNB"))
无论是[person sayHello]还是objc_msgSendSuper(&lgsuper, sel_registerName("sayHello"))执行的都是父类中的实现

3.objc_msgSend源码查看

搜索objc_msgSend,打开源码文件objc-msg-arm64.s
_objc_msgSend

对应流程图.png
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check 判断接受者是否存在
#if SUPPORT_TAGGED_POINTERS //是否支持tagged_pointers对象
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero    //直接返回空 LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa 
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class  
LGetIsaDone:   //获取isa完毕LGetIsaDone
    // calls imp or objc_msgSend_uncached
      //开启缓存查找流程
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

LNilOrTagged

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:  //小对象或者空判断
    b.eq    LReturnZero     // nil check 直接返回空了LReturnZero
    GetTaggedClass
    b   LGetIsaDone  //小对象ISA处理
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero

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

CacheLookup

开启缓存查找流程.png
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

    mov x15, x16            // stash the original isa
LLookupStart\Function:
    // p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    ldr p10, [x16, #CACHE]              // p10 = mask|buckets 
    lsr p11, p10, #48           // p11 = mask
    and p10, p10, #0xffffffffffff   // p10 = buckets
    and w12, w1, w11            // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
    tbnz    p11, #0, LLookupPreopt\Function
#endif
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    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

    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b

    // wrap-around:
    //   p10 = first bucket
    //   p11 = mask (and maybe other bits on LP64)
    //   p12 = _cmd & mask
    //
    // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
    // So stop when we circle back to the first probed bucket
    // rather than when hitting the first bucket again.
    //
    // Note that we might probe the initial bucket twice
    // when the first probed slot is the last entry.


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    add p13, p10, w11, UXTW #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p13, p10, p11, LSL #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

LLookupEnd\Function:

objc_msgSend总体流程图:

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

推荐阅读更多精彩内容