Runtime
c c++ 汇编一起写成的api,为OC提供运行时。
与运行时相对应的是编译时:源代码翻译成机器可识别的语言(汇编),最后翻译成二进制代码。
代码运行起来时,运行时会把可执行文件装载到内存中。
运行时版本:
Legacy and Modern Versions
Objective-C 2.0 开始就是Modern版本的运行时
old 和 new是为了兼容两个版本,现在主要用Modern版本。
在Main函数里面添加代码。打开终端
生成 Main.cpp
打开main.cpp 9万多行代码,前面是为了提供运行环境。
任何方法的调用,都会编译局成objc_msgSend
RuntimePerson *person = ((RuntimePerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("RuntimePerson"), sel_registerName("new"));
OC对象的本质就是结构体,方法的本质是发送消息。
消息的组成(2个参数):
(id)objc_getClass("RuntimePerson") 消息接受者
sel_registerName("new") 方法编号
runtime三种调用方式:
runtime api
NSObject api isMemeber isClass
OC上层方法 @selector
这些对象,类,父类是如何发送消息的呢?
导入头文件
#import <objc/message.h>
((id (*)(id, SEL ))objc_msgSend)(person, NSSelectorFromString(@"run")); //向对象发送消息
person 消息接受者
SEL方法编号
((id (*)(id, SEL ))objc_msgSend)(objc_getClass("RuntimePerson"), NSSelectorFromString(@"walk")); //向类发送消息
struct objc_super mySuper;
mySuper.receiver = student;
mySuper.super_class = class_getSuperclass([student class]);
((id (*)(id, SEL ))objc_msgSendSuper)((__bridge id)(&mySuper), @selector(run)); //向父类发送对象消息
struct objc_super myClassSuper;
myClassSuper.receiver = [student class];
myClassSuper.super_class = class_getSuperclass(object_getClass([student class]));
((id (*)(id, SEL ))objc_msgSendSuper)((__bridge id)(&myClassSuper), @selector(walk));
//向父类发送类消息(类方法)
注意objc_msgSendSuper传的是结构体指针,是对结构体指针发送的run方法需要注意,结构体指针源码如下:
/// 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; //如果不是objc2
#else
__unsafe_unretained _Nonnull Class super_class; //如果是objc2
#endif
/* super_class is the first class to search */
};
#endif
//对象方法存在哪里
//类方法存在哪
//类方法存在原类里面,以什么姿态存在?以实例方法是形式存在原类里面。
objc_msgSend 发送消息很快 快速(用汇编在缓存中查找)和慢速(通过C配合C++和汇编一起查找方法)两种方式
cache 存储SEL和IMP
方法查找的时候首先查找cache(哈希表),如果有直接返回。如果没有就通过c语言缓慢查找,如果找到了又会存到cache,方便下次快速查找。如果c语言也没找到,就会经过另外一个复杂的过程。
objc_msgSend为什么要用汇编写?
原因有两个:
1. c语言不可能通过写一个函数,保留未知的参数,跳转到任意的指针。(通过SEL,id,对象传进来,这些都是未知的,只有通过运行时才知道,c语言是无法实现的)
2. 速度快,oc要先转换成c或者c++而 c c++还需要编译,使用汇编可以直接操作寄存器,所以速度会更快。
源码查找汇编代码:
_objc_msgSend
选择arm64架构
找到objc_msgSend汇编入口 ENTRY _objc_msgSend
分析源码:
UNWIND _objc_msgSend, NoFrame 窗口为0
tagged pointer是特殊的数据类型,比较小的数据类型例如NSDate类型,没有必要用64位数据去存储,所以使用tagged pointer去存储。
b.le LNilOrTagged 跳转到LNilOrTagged
b.eq LReturnZero 如果对象是nil,不需要发送消息,直接返回。
从b.ne LGetIsaDone可以看出对isa做了相关处理。
结束消息发送
isa处理完毕后,会调用CacheLookup,NORMAl(缓存中找imp)normal为传的值 后面会出现两种情况直接调用imp或者调用 objc_msgSend_uncached(缓存中没有这个方法)
下面看下CacheLookup:
有三种形式NORMAL, GETIMP, LOOKUP
.macro CacheLookup宏定义
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
第一种情况:缓存找到,返回
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
第二种情况:没找到
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
第三种情况:找到了,add imp到cache里面
.macro CacheHit 宏定义
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
如果为normal直接调用imp
.macro CheckMiss宏定义
如果找不到,并且传了NORMAL __objc_msgSend_uncached 会被调用
UNWIND __objc_msgSend_uncached, FrameWithNoSaves window没必要处理
MethodTableLookup 方法列表查找(核心方法)直接操作内存和寄存机非常快 比c快0.5倍到0.8倍
bl __class_lookupMethodAndLoadCache3 bl跳转__class_lookupMethodAndLoadCache3,在寻找这个方法的时候会发现找不到容易失去信心。
其实这里是跳转的c语言,需要去掉一个_ 搜索 _class_lookupMethodAndLoadCache3
name 方法名 imp 方法哈希表 name和imp是键值对
这里的YES,NO,YES啥意思,initialize代表已经编译过了,在cache里面没有所以是NO,resolver是yes代表是否实现了这个类
下面是 c和c++查找方法的过程 lookUpImpOrForward方法源码:
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) { // 可能有缓存 -- NO
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls); 判断是否已经声明,或是是一个已知的类,如果是未知的类会报出相应的错误信息
//
if (!cls->isRealized()) { //如果是已知的类,判断是否已经实现了类,如果没有实现就去实现,给相关的内部DATA赋值
// DATA
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) { //如果需要初始化,并且没有有初始化则进行类初始化。
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// 重点
retry:
runtimeLock.assertLocked();
// Try this class's cache.
// 这里为什么又重新去取imp ? 为什么这样设计?
// 1. 并发,多线程,资源抢夺,已经更新了cahce
// remap(cls)这个方法在加载类的手会重映射,可能就有了方法实现,所以要在这里重新cache_getImp
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{ //首先在自己本类查找是否有相关方法sel
Method meth = getMethodNoSuper_nolock(cls, sel); //后面有这个方法的分析
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls); 找到之后直接缓存赋值,下次直接总cache里面汇编查找(CacheHit)。
imp = meth->imp;
goto done;
}
}
如果从本类里面没有查找到相关方法,那么就查找父类里面是否有相关实现
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass) 直到curClass为nil,因为NSObject的superClass 为nil,所以最终直到NSObject
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list."); 内存溢出
}
// Superclass cache.
imp = cache_getImp(curClass, sel); 从父类cache里面查找imp
if (imp) { 如果找到了imp
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass); 添加到缓存中去
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
如果缓存没找到,就从本类的类方法中查找
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass); 添加到缓存中去
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once. 如果没有找到imp实现,就会尝试动态方法解析执行一次
// 类方法的查找 -- 汇编
// 找父类方法 没有
// 动态方法解析
// 类的类方法 = 元类实例方法 -(void)run{} -- NSObject
// +(void)run{}
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); //后面有源码解析
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
首先判断了cahce是否有缓存,如果传了YES会走这个汇编方法,快速查找方法
查找本类里里面的sel方法:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
//从begin到end遍历查找method,找到返回,没有找到返回nil
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
总结:思维导图
如果汇编和c c++都没有找到方法,那么会走方法动态解析的流程:
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod. 如果方法没有实现就会调用resolveClassMethod(类方法动态解析),resolveInstanceMethod(实例方法动态解析)这个两个方法
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { 首先判断不是元类
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else { 是元类
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// LGPERSON(类方法) - 元类(实例) - 根元类(实例) -- NSObject (实例方法)
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
经过尝试,有效,这里出现了一个新的问题就是动态方法解析调用了两次
第一次发送是在_class_resolveMethod 方法里面通过下面的代码发送第一次消息:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
第二次 从下面的截图可以发现是在forwarding消息转发的时候又调用了一次resolveClassMethod 所以一共调用两次动态解析方法
消息转发流程:在”消息无法处理“的时候又会调用一次resolveINstanceMethod
下面在动态解析方法里面处理添加先未实现的方法:
#import "RuntimePerson.h"
@implementation RuntimePerson
//-(void)run{
// NSLog(@"%s", __func__);
// NSLog(@"RuntimePerson -- %s", __func__);
//}
//+(void)walk{
// NSLog(@"RuntimePerson -- %s", __func__);
//}
-(void)readBook {
NSLog(@"读书");
}
+(void)helloword {
NSLog(@"helloword");
}
#pragma mark - 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(run)) {
NSLog(@"对象方法解析走这里");
SEL readSEL = @selector(readBook);
Method readM = class_getInstanceMethod(self, readSEL);
IMP readImp = method_getImplementation(readM);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel{
// NSLog(@"来了 老弟 %s", __func__);
if(sel == @selector(walk)) {
NSLog(@"类方法解析走这里");
SEL hellowordSEL = @selector(helloword);
//类方法就存在我们的原类的方法列表
Method hellowordM1 = class_getClassMethod(self, hellowordSEL);
Method hellowordM = class_getInstanceMethod(object_getClass(self), hellowordSEL);
IMP hellowordImp = method_getImplementation(hellowordM);
const char *type = method_getTypeEncoding(hellowordM);
NSLog(@"%s", type);
return class_addMethod(object_getClass(self), sel, hellowordImp, type);
}
return [super resolveClassMethod:sel];
}
@end
这里发现一个问题,类的类方法和元类的对象方法指针地址是相同的,所以是同一个东西。
总结:在
这里有个问题就是为什么会再次调用_class_resolveInstanceMethod
如果动态方法决议没有成功后面会继续走下层处理(消息转发):
////对象方法转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(run)) {
// 转发给我们的LGStudent 对象
return [RuntimeDog new];
}
return [super forwardingTargetForSelector:aSelector];
}
//类方法转发流程 如果这里返回YES,就不会走后面两个方法了
+ (id)forwardingTargetForSelector:(SEL)aSelector { 在这里可以做一系列自定义处理和crash收集,还可以防止奔溃
NSLog(@"%s", __func__);
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(walk)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{ 消息转发
NSLog(@"%s",__func__);
可以在这里打印系统调用堆栈,收集起来,发送给服务器,bugly 友盟等第三方的做法
另外切面编程(aspect)也会用到这里的消息转发
或者是数组字典的一些边界和非空校验也可能会用到消息转发
//转发给person的readBook方法
NSString *sto = @"奔跑少年";
anInvocation.target = [RuntimePerson class];
// [anInvocation setArgument:&sto atIndex:2];
NSLog(@"%@",anInvocation.methodSignature);
anInvocation.selector = @selector(readBook);
[anInvocation invoke];
}
调用顺序
forwardingTargetForSelector -> methodSignatureForSelector(方法签名) -> forwardInvocation(消息转发)
下面探索下源码:
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
_objc_msgForward_impcache这部分是汇编,需要前面加个_搜索
跳转到 __objc_msgForward
__objc_forward_handler 回调信息,这里就是我们平时方法没有实现报错信息的打印。
beq __objc_msgForward 跳转到了__objc_msgForward 方法
动态方法解析只有汇编调用,没有源码实现 (是闭源的)
这里使用系统提供的一个函数instrumentObjcMessageSends(一般在系统内部使用) 可以打印所有的调用信息,保存到一个文件中/private/tmp在这个路径下面
在mac APP项目中可以生成,但是在iOS app工程中无法生成(具体原因还不清楚)