前言
上节通过
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的发起发起方式分三种
OC方法
NSObject方法
objc api
接下里我们落地搞一下
我们定一个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
下面我们通过
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
objc_super通过源码查一下
我们通过自己创建代码实现一下 看打印结果是否相同
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
运行项目 跑汇编断点
汇编显示
objc_msgSend在libobjc.A.dylib系统库 然后在objc源码种全局搜索objc_msgSend,找到真机的汇编objc-msg-arm64.s
下面我们看一下汇编代码
判断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如下图:
下面我们要走
__LP64__
GetClassFromIsa_p16核心功能就是获取class存放到p16
ExtractISA 通过isa&ISA_MASK(0x007ffffffffffff8)返回class