OC底层原理07- objc_msgSend上

361 阅读5分钟

前言

上节通过cache我们过度到 objc_msgSend

  • Cache readers (PC-checked by collecting_in_critical())
  • objc_msgSend
  • cache_getImp
  • Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
  • cache_t::copyCacheNolock (caller must hold the lock)
  • cache_t::eraseNolock (caller must hold the lock)
  • cache_t::collectNolock (caller must hold the lock)
  • cache_t::insert (acquires lock)
  • cache_t::destroy (acquires lock)
  • UNPROTECTED cache readers (NOT thread-safe; used for debug info only)
  • cache_print
  • _class_printMethodCaches
  • _class_printDuplicateCacheEntries
  • _class_printMethodCacheStatistics

一.runtime的运行时理解

1.编译时 顾名思义就是正在编译的时候 . 那啥叫编译呢?就是编译器帮你把 源代码翻译成机器能识别的代码

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

3.Runtime有两个版本 ⼀个Legacy版本(早期版本) ,⼀个Modern版本(现⾏版本)

  • 早期版本对应的编程接⼝:Objective-C 1.0
  • 现⾏版本对应的编程接⼝:Objective-C 2.0
  • 早期版本⽤于Objective-C 1.0, 32位的Mac OS X的平台上
  • 现⾏版本:iPhone程序和Mac OS X v10.5 及以后的系统中的 64 位程序 官网runtime文档:Objective-C Runtime Programming Guide

二.objc_msgSend流程

runtime的发起发起方式分三种 Xnip2021-06-27_23-17-20.jpg

  • OC 方法

Xnip2021-06-27_23-18-56.jpg

  • NSObject 方法

Xnip2021-06-27_23-19-02.jpg

  • objc api

Xnip2021-06-27_23-19-11.jpg

接下里我们落地搞一下

我们定一个LGPerson类 继承NSObject

@interface LGPerson : NSObject
//无参数 无返回值
-(void)sayGo;
//无参数 有返回值
- (NSString *)sayHello;
//有参数 无返回值
- (void)sayNB:(NSString *)name age:(int)age ;
//有参数 有返回值
-(NSString *)sayWord:(NSString *)name age:(int)age;
@end

@implementation LGPerson
-(void)sayGo
{
    NSLog(@"sayGo");
}
- (NSString *)sayHello
{
    NSLog(@"sayHello");
    return @"sayHello";
}
- (void)sayNB:(NSString *)name age:(int)age{
    NSLog(@"%@-%d",name,age);
}
-(NSString *)sayWord:(NSString *)name age:(int)age
{
    NSLog(@"%@-%d",name,age);
    return [NSString stringWithFormat:@"%@-%d",name,age];
}
@end

main里面调用方法 发现person调用上面的方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         LGPerson *person = [LGPerson alloc];
        [person sayGo];
        [person sayHello];
        [person sayNB:@"xiaowen" age:20];
        [person sayWord:@"xiaoLi" age:18];
        
         
    }
    return 0;
}

下面我们通过clang xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp 生成 main.cpp 找到main函数看看里面都做了什么

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("sayGo"));
        ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
        ((void (*)(id, SEL, NSString *, int))(void *)objc_msgSend)((id)person, sel_registerName("sayNB:age:"), (NSString *)&__NSConstantStringImpl__var_folders_38_skzq8rts68505r6rln9p42_00000gn_T_main_158654_mi_4, 20);
        ((NSString *(*)(id, SEL, NSString *, int))(void *)objc_msgSend)((id)person, sel_registerName("sayWord:age:"), (NSString *)&__NSConstantStringImpl__var_folders_38_skzq8rts68505r6rln9p42_00000gn_T_main_158654_mi_5, 18);
    }
    return 0;
}

编译上层代码的到一个解释: 调用方法 = 消息发送 : objc_msgSend(消息的接受者,消息的主体(sel + 参数))

调用objc_msgSend的方法

1.必须倒入相应的头文件import<objc/message.h>

2.需要设置Xcode如下图 target--> Build Setting --> 搜索objc_msgSend --> Enable strict checking of objc_msgSend Calls 设置额为 NO Xnip2021-06-27_23-42-58.jpg 下面我们通过objc_msgSend调用上面的对象方法 看是否打印的结果一样

        [person sayGo];
        objc_msgSend(person, @selector(sayGo));
        [person sayHello];
        objc_msgSend(person, @selector(sayHello));
        [person sayNB:@"xiaowen" age:20];
        objc_msgSend(person, @selector(sayNB:age:),@"xiaowen",20);
        [person sayWord:@"xiaoLi" age:18];
        objc_msgSend(person, @selector(sayWord:age:),@"xiaoli",20);

结果如下打印的结果是一样的

2021-06-28 00:10:11.294330+0800 001-运行时感受[61297:7175511] sayGo
2021-06-28 00:10:11.294713+0800 001-运行时感受[61297:7175511] sayGo
2021-06-28 00:10:11.294765+0800 001-运行时感受[61297:7175511] sayHello
2021-06-28 00:10:11.294809+0800 001-运行时感受[61297:7175511] sayHello
2021-06-28 00:10:11.294882+0800 001-运行时感受[61297:7175511] xiaowen-20
2021-06-28 00:10:11.294913+0800 001-运行时感受[61297:7175511] xiaowen-20
2021-06-28 00:10:11.294932+0800 001-运行时感受[61297:7175511] xiaoLi-18
2021-06-28 00:10:11.294987+0800 001-运行时感受[61297:7175511] xiaoli-18

下面我们搞一下 LGPerson 继承 LGTeacher

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

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


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

@implementation LGPerson
@end

我们在main 里面调用sayHello

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      LGPerson *person = [LGPerson alloc];
      [person sayHello];//子类没有 调用父类的sayHello 方法 
    }
    return 0;
}

运行 打印结果:[LGTeacher sayHello]

下面我们通过objc_msgSend如何调用父类的方法 我们发现有一个叫objc_msgSendSuper 我们通过源码查一下objc_msgSendSuper

Xnip2021-06-28_00-28-58.jpg

objc_super通过源码查一下

Xnip2021-06-28_00-29-49.jpg

我们通过自己创建代码实现一下 看打印结果是否相同

int main(int argc, const char * argv[]) {
    @autoreleasepool {
     LGPerson *person = [LGPerson alloc];
     [person sayHello];
     struct objc_super kc_objc_super;
     kc_objc_super.receiver = person;
     kc_objc_super.super_class = LGPerson.class/LGTeacher.class 都可以;
     objc_msgSendSuper(&kc_objc_super,@selector(sayHello));
   }
    return 0;
}

打印结果

2021-06-28 00:31:27.727423+0800 001-运行时感受[61426:7194388][LGTeacher sayHello]
2021-06-28 00:31:27.727898+0800 001-运行时感受[61426:7194388][LGTeacher sayHello]

三.通过汇编探索一下objc_msgSend

运行项目 跑汇编断点 Xnip2021-06-29_20-46-56.jpg

Xnip2021-06-29_20-47-37.jpg 汇编显示objc_msgSendlibobjc.A.dylib系统库 然后在objc源码种全局搜索objc_msgSend,找到真机的汇编objc-msg-arm64.s

Xnip2021-06-28_00-43-50.jpg

Xnip2021-06-28_00-44-47.jpg

Xnip2021-06-29_20-56-49.jpg

下面我们看一下汇编代码

Xnip2021-06-29_21-15-42.jpg

判断receiver是否等于nil, 在判断是否支持Taggedpointer小对象类型

支持Taggedpointer小对象类型,小对象为空 ,返回nil,不为nil处理isa获取class跳转CacheLookup流程 不支持Taggedpointer小对象类型且receiver = nil,跳转LReturnZero流程返回nil 不支持Taggedpointer小对象类型且receiver != nil,通过GetClassFromIsa_p16把获取到class 存放在p16的寄存器中,然后走CacheLookup流程

GetClassFromIsa_p16 获取Class SUPPORT_INDEXED_ISA = 0如下图:

Xnip2021-07-01_10-20-35.jpg 下面我们要走 __LP64__ Xnip2021-06-29_21-13-08.jpg

GetClassFromIsa_p16核心功能就是获取class存放到p16

Xnip2021-06-29_21-08-49.jpg

ExtractISA 通过isa&ISA_MASK(0x007ffffffffffff8)返回class