Runtime简析及OC消息机制

315 阅读7分钟

一、Runtime相关基本概念

runtime 是C、C++、汇编一起写成的一套api,为Objectvie-C提供了运行时机制。

1、运行时

运行时是指一个程序在运行(或者在被执行)的状态。

  • 特点:
    • 装载内存
    • 运行时功能---依赖于runtime提供的api

2、编译时

将高级语言翻译成机器能够识别的机器语言

  • 特点:
    • 编译时类型检查或静态类型检查
    • 机器语言---二进制

3、对象本质

  • 结构体
    • isa
    • objc_object、NSObject_IVARS...
    • Method、Ivar、Category、objc_property_t
  • OC 中实例、类、元类的关系图

4、方法本质

发送消息 objc_msgSend(id self,SEL _cmd)

  • 参数一:id self --- 消息接收者
  • 参数二:SEL _cmd --- 方法编号,底层是一个字符串,即方法名称
  • imp --- 函数实现的指针
  • sel--->imp ? 如何通过方法编号找到函数实现??

1.对象方法 --- 存在类中 以实例方法的形态存在

  • A、调用本类对象方法 --- objc_msgSend(s, sel_registerName("run"));
    LGStudent *s = [LGStudent new];
    [s run];
    // 方法调用底层编译
    // 方法的本质: 消息 : 消息接受者 消息编号 ....参数 (消息体)
    objc_msgSend(s, sel_registerName("run"));
  • B、调用父类对象方法 --- objc_msgSendSuper(&mySuper,@selector(run));
     // 向父类发消息(对象方法)
	LGStudent *s = [LGStudent new];
	struct objc_super mySuper;
	mySuper.receiver = s;
	mySuper.super_class = class_getSuperclass([s class]);
	objc_msgSendSuper(&mySuper, @selector(run));

2.类方法 --- 存在元类中 以实例方法的姿态存在

  • A、调用本类类方法 --- objc_msgSend(objc_getClass("LGStudent"),sel_registerName("walk"));
    // 类方法编译底层
	id cls = [LGStudent class];
	void *pointA = &cls;
	[(__bridge id)pointA walk];
	objc_msgSend(objc_getClass("LGStudent"), sel_registerName("walk"));
  • B、调用父类类方法 --- objc_msgSendSuper(&myClassSuper,sel_registerName("walk"));
	  //向父类发消息(类方法)
	  	LGStudent *s = [LGStudent new];
	   struct objc_super myClassSuper;
	   myClassSuper.receiver = [s class];
	   myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));// 元类
	   objc_msgSendSuper(&myClassSuper, sel_registerName("walk"));

5、runtime三种调用方式

  • 1、runtime api --- 运行时系统提供的api 接口
  • 2、NSObject 提供的Api
  • 3、OC 提供的api @selecter ...

二、OC消息机制

1、消息发送---objc_msgSend

OC方法的调用即为消息发送,底层都会走objc_msgSend(id self,SEL _cmd)方法,而objc_msgSend(id self,SEL _cmd)方法实现使用汇编写成,为啥使用汇编而不用C,C++等高级语言?

  • 1.汇编写成原因
    • 寄存器 --- 保留不确定个数的未知参数,还可以跳转到任意的指针, x0~x31
    • 效率高 比 C/C++ 快0.5-0.8倍
    1. C,C++等高级语言做不到
    • C等高级语言做不到写一个函数,保留不确定个数的未知参数,还可以跳转到任意的指针;

2、方法查找 --- 发送消息的两种方式,快速查找和慢速查找

消息发送时如何通过方法编号sel找到对应的方法实现imp才是问题的关键,以下是消息机制中查找imp的过程简述:

2.1、快速查找
  • 源码中 id objc_msgSend(id self, SEL _cmd, ...)方法, 汇编跟进去到 ENTRY _objc_msgSend,在Apple GithubApple OpenSource上有源码,但是需要自己编译。
  • OC中类的定义objc_class结构体中的 cache_t cache,缓存了方法的sel和imp等数据(sel 与 imp 一一对应生成的哈希表), 快速查找即在此类的缓存中查找;
  • ENTRY _objc_msgSend 入口
    • 1:LNilOrTagged --- 如果指针小于等于 LNilOrTagged 直接return返回
    • 2:LGetIsaDone isa 处理完毕,通过isa 找到相应类class
    • 3:CacheLookup NORMAL --- 缓存查找 imp
      • 1.CacheHit --- 查找到相应的imp,直接调用即:call imp
      • 2.CheckMiss
        • 2.1 CheckMiss NORMAL --- __objc_msgSend_uncached(找不到处理,就是说没有相应缓存)
          • a.接下来去MethodTableLookup 方法列表查找
          • b.__class_lookupMethodAndLoadCache3 --- 此方法为C的方法,汇编查找不到相应imp,从此时开始进入慢速查找
        • 2.2 CheckMiss GETIMP --- LGetImpMiss
        • 2.3 CheckMiss LOOKUP --- __objc_msgLookup_uncached
      • 3.add
        • 3.1 CacheHit
        • 3.2 CheckMiss
        • 3.3 JumpMiss
2.2、慢速查找

_class_lookupMethodAndLoadCache3方法跳转到C / C++中,此方法中直接调用lookUpImpOrForward:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{   // 因为我们查找了没有就没必要传参cache
    return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
  • lookUpImpOrForward 部分代码逻辑
    • realizeClass(cls)
    • _class_initialize (_class_getNonMetaClass(cls, inst))
    • retry
      • cache_getImp(cls, sel)
        • 设计原因:
          • 1.并发,走到这时已经有缓存了;
          • 2.classRemaped 重映射
      • 漫长寻找imp的过程,方法查找流程图:
  // Try this class's method lists.从自己的方法列表中寻找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);//如果找到了就去填充到缓存
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.从递归父类去找知道NSObject
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // 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);
            if (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;
            }
        }
    }
2.3、动态方法解析
2.3.1 动态方法解析源码分析

快速查找和慢速查找都没有找到,走这里

// No implementation found. Try method resolver once.
    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;
    }
  • _class_resolveMethod(cls, sel, inst) --- 动态方法解析调用
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +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]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
  • 不是元类时调用 _class_resolveInstanceMethod(cls, sel, inst)

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);/// 这里系统通过消息发送调用了一次SEL_resolveInstanceMethod

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  • 是元类时调用 _class_resolveClassMethod(cls, sel, inst),当依旧找不到imp时,再调用 _class_resolveInstanceMethod(cls, sel, inst);
/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst),  SEL_resolveClassMethod, sel);///这里系统通过消息发送调用了一次SEL_resolveClassMethod

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

2.3.2 动态方法解析实现处理
#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{
    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];
}
2.4、消息转发
2.4.1 消息转发源码分析

动态方法解析没有实现处理,就会进入消息转发

// No implementation found, and method resolver didn't help. 
    // Use forwarding.
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
  • _objc_msgForward_impcache --- 只有汇编调用 没有源码实现
  • 可以通过instrumentObjcMessageSends 方法,打印出方法执行过程中调用的所有OC方法
2.4.2 消息转发实现处理

消息转发流程图

  • forwardingTargetForSelector 消息转发到新的target,若未对消息进行处理,则会再次调用
+ (id)forwardingTargetForSelector:(SEL)aSelector{
    // 自定义处理 -- crash
    // 防止奔溃
    //  if (aSelector == @selector(walk)) {
    // 转发给我们的LGStudent 对象
    //      return [LGStudent new];
    //  }
    NSLog(@"%s",__func__);
    return [super forwardingTargetForSelector:aSelector];
}
  • methodSignatureForSelector --- 若forwardingTargetForSelector 没有实现消息转发,就只能方法签名了,方法重新签名可能会改变方法的type,根据需要响应的方法确定type
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    // 处理方法签名
    NSLog(@"%s",__func__);
    if (aSelector == @selector(walk)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}
  • forwardInvocation 对重新签名(改变type)过的sel,进行消息转发
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
    
    NSString *sto = @"奔跑少年";
    anInvocation.target = [LGStudent class];
    [anInvocation setArgument:&sto atIndex:2];
    NSLog(@"%@",anInvocation.methodSignature);
    anInvocation.selector = @selector(run:);
    [anInvocation invoke];
}

由于本人水平有限,文中如有不足之处,望大神指出。
如果你看完后觉得对你有所帮助,勿忘点赞+关注
赠人玫瑰,手有余香。