美文网首页RunTime面试
Objective-c Runtime 消息发送机制

Objective-c Runtime 消息发送机制

作者: Mr_差不多 | 来源:发表于2018-03-13 19:32 被阅读35次

Objective-c 类的结构

Objective-c 作为一种动态语言,除了需要一个编译器来编译之外,还需要有一套运行时环境来动态的创建类和对象。这就是我们要了解的Runtime。Runtime的核心是消息传递(Messaging),了解这一核心能够更好的利用语言的特点,适当的时候进行扩展。

与静态语言不同的是,Objective-c在调用方法的时候并不是直接跳转到编译时生成的函数地址执行代码,而是会把这个方法以消息的形式发送给object。能否由object执行或者转发给别的对象处理或者不做处理,都是在运行时决定的。

打开objc/runtime.h可以看到,系统声明了一个objc_class的结构体


struct objc_class {

    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

    Class _Nullable super_class                              OBJC2_UNAVAILABLE;

    const char * _Nonnull name                              OBJC2_UNAVAILABLE;

    long version                                            OBJC2_UNAVAILABLE;

    long info                                                OBJC2_UNAVAILABLE;

    long instance_size                                      OBJC2_UNAVAILABLE;

    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;

    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;

    struct objc_cache * _Nonnull cache                      OBJC2_UNAVAILABLE;

    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

从结构体中可以看出,一个运行时类关联了它的父类指针,类名,版本号,成员变量,方法列表(实例方法),缓存,协议。

其中成员变量,方法列表都是一个结构体类型


//成员变量和成员变量列表

struct objc_ivar {

    char * _Nullable ivar_name                              OBJC2_UNAVAILABLE;

    char * _Nullable ivar_type                              OBJC2_UNAVAILABLE;

    int ivar_offset                                          OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

}                                                            OBJC2_UNAVAILABLE;

struct objc_ivar_list {

    int ivar_count                                          OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

    /* variable length structure */

    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;

}                                                            OBJC2_UNAVAILABLE;

//方法和方法列表

struct objc_method {

    SEL _Nonnull method_name                                OBJC2_UNAVAILABLE;

    char * _Nullable method_types                            OBJC2_UNAVAILABLE;

    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;

} 

struct objc_method_list {

    struct objc_method_list * _Nullable obsolete            OBJC2_UNAVAILABLE;

    int method_count                                        OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

    /* variable length structure */

    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;

}                                                            OBJC2_UNAVAILABLE;

objc_ivar_list和objc_method_list是分别用来存储成员变量和方法的列表,这两个结构体内部有objc_ivar,和objc_method这两个结构体,这两个结构体存储的是对应的成员变量和方法。其中objc_method这个结构体有SEL method_name 和 IMP method_imp这两个变量,分别对应方法的名称和实现。

SEL是selector在oc中的表示,本质是一个映射到方法的一个C字符串。

IMP是一个函数指针,指向一个函数的实现

这里还要再了解一个c结构体objc_object


struct objc_object { 

    Class isa  OBJC_ISA_AVAILABILITY;

};

这个结构体对应的是我们类的实例,结构体内部只有一个isa属性,指向的正是objc_class这个结构体,而objc_class这个结构体中也有一个isa指针说明,类本身也是一种对象,这个isa指向的是类对象的元类Meta Class,Meta Class 表述了类对象本身所具备的元数据。那既然类本身作为一个对象存在,那类方法是不是就相当于是类对象的实例方法那?我们通过下面的代码来测试一下:


Test *test = [[Test alloc] init];

unsigned int count = 0;

Method *meths = class_copyMethodList(object_getClass([test class]), &count);

for (NSInteger i = 0; i < count; i++) {

    Method meth = meths[i];

    SEL sel = method_getName(meth);

    const char *name = sel_getName(sel);

    NSLog(@"%s", name);

}

free(meths);

runtime.h中提供了很多方便的方法使我们可以轻易的获取方法列表或者成员变量,其中以class_开头的是跟objc_class相关的方法,以object_开头的是跟objc_object相关的方法,分别对应我们的类对象和实例对象。通过上面的object_getClass方法可以获取需要的类,传入的一个obj对象,如果传入的是test则打印出来的方法名是实例方法,如果传入的是test的类则打印出来的是类方法,通过这个测试也验证了上面的猜测。

了解了类的构成之后,那么在调用方法的时候是怎么处理的那?oc的运行时特性又是怎么体现出来的那?

消息发送

事实上,在编译时,oc的调用方法会被翻译成一个c的函数调用,objc_msgSend(receiver, selector, arg1, arg2, ...).

从上面的分析可以看出,发送一条消息应该执行下列步骤:

1. 通过obj的isa找到实例的class

2. 在class的方法列表中找对应的方法

3. 如果没找到就去superclass里继续找

4. 一旦找到就去执行它的实现IMP

一个正常的方法通常都是这样被调用的,而且由于不同的方法调用频率是不一样的,所以系统提供了一个cache也就是上面看到的objc_cache来做缓存,提高查询效率。

动态方法解析,消息转发和方法签名

上面的例子只是针对正常的情况,那如果该方法没找到怎么办,这时就会抛出unrecognized selector sent to … 的异常,这也是我们在开发中经常遇到的情况。那怎么避免这种情况那,这就用到了oc的动态特性,在一场抛出前,runtime会给三次补救的机会:

1.动态方法解析

查看NSObject的API发现有下面这两个方法+resolveInstanceMethod:和+resolveClassMethod:分别对应实例方法和类方法。在没有找到方法的情况下,如果实现了这两个方法中的一个,那么系统就会重新启动一次消息发送的过程。

以上面的Test类为例,如果一个Test的实例调用一个method1:方法,而Test类没有实现的话使用下面的代码可以避免程序闪退:


void method2(id obj, SEL _cmd)

{

    NSLog(@"do something");

}

+(BOOL)resolveInstanceMethod:(SEL)aSEL

{

    if(aSEL == @selector(method1:))

    {

        class_addMethod([self class],aSEL,(IMP)method2,@"v@:");

        return YES;

    }

    return [super resloveInstanceMethod];

}

2.如果resolve方法返回NO,就会进行下一步,消息转发(Message Forwarding)

同样runtime提供了一个方法,-forwardingTargetForSelector: ,用这个方法可以获得把消息转发给其他对象的机会,该方法返回一个实现了被调用的方法的实例:


- (id)forwardingTargetForSelector:(SEL)aSelector

{

    if (aSelector == @selector(method1:))

    {

        return xxxObject;

    }

    return [super forwardingTargetForSelector:aSelector];

}

3.如果这个方法也返回nil,则会启动最后一步,方法签名,首先runtime会发送一个-methodSignatureForSelector:消息来获取参数和返回值,如果返回nil则发送doesNotRecognizeSelector:消息,这是程序也就崩溃了。如果返回了一个函数签名,runtime就会创建一个NSInvocation对象,发送-forwardInvocation:消息给当前对象,并传入NSInvocation:


- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector

{

    NSMethodSignature *signature = [super methodSignatureForSelector:selector];

    if(!signature) {

        if([xxxObj respondsToSelector:selector]) {

            return [xxxObj methodSignatureForSelector:selector];

        }

    }

    return signature;

}

- (void)forwardInvocation:(NSInvocation*)invocation

{

    if ([xxxObj respondsToSelector:[invocation selector]]) {

        [invocation invokeWithTarget:xxxObj];

    }else {

        [self doesNotRecognizeSelector:sel];

    }

}

这就是Objective-c关于消息发送的流程,利用这个流程和运行时的特性可以对语音进行扩展和解决一些问题。


Refer:

Objective C Runtime

iOS开发-Runtime详解(简书) - 一个低调的iOS开发 - 博客园

相关文章

  • 2018-02-01

    《Objective-C runtime系列 1》消息发送及转发机制 Objective-C是基于C,加入了面...

  • swift学习之Selector

    在Objective-c中,@selector作为方法选择器基于runtime的消息机制,在运行时通过发送消息,寻...

  • Runtime 消息机制

    Objective-C Runtime 消息机制 最近在找工作,Objective-C中的Runtime是经...

  • iOS复习笔记

    runtime机制 objective-c代码总是先预编译成C代码,runtime机制也是基于C的实现。消息机制是...

  • runtime 消息机制简析

    runtime 消息机制消息机制可以简单分为三个方面:消息发送、动态方法解析、消息转发一.消息发送oc 中所有的方...

  • 深入理解runTime

    Objective-C Runtime 引言 Objective-C的方法调用实则为“发送消息”,我们来看[dog...

  • runtime的实用性讲解

    runtime 概念 runtime 运行时机制,主要是消息机制。OC 中的方法调用属于消息的发送,动态调用过程,...

  • Runtime 03 - objc_msgSend、super

    Runtime 03 - objc_msgSend、super Objective-C 的消息机制 Objecti...

  • 方法调用底层实现

    runtime怎么实现方法的调用 :消息机制,runtime系统会把方法调用转化为消息发送。即objc-msgSe...

  • RunTime

    1.使用消息发送机制创建对象,给对象发送消息 2. runTime方法交换的使用 3. KVO本质其实也是runtime

网友评论

    本文标题:Objective-c Runtime 消息发送机制

    本文链接:https://www.haomeiwen.com/subject/dhltqftx.html