美文网首页
iOS runtime

iOS runtime

作者: 罗淞 | 来源:发表于2017-07-07 17:40 被阅读17次

    学习runtime,主要参考的是@SOI的iOS runtime和runloop
    http://www.jianshu.com/p/ebc6e20b84cf
    自己写一遍和阅读一遍还是有很大的区别。
    也参考了@CornBallast的讲解,很风趣,讲的很明白。
    http://www.jianshu.com/p/88d11bb12ba1

    Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,它不仅需要一个编译器,还需要RunTime系统来动态创建类和对象,进行消息的发送和转发。
    在使用runtime之前,我们需要引入头文件

    #import "objc/objc-runtime.h"
    

    先看一个问题:
    创建一个Person的类,然后创建一个Person对象。执行一下操作:

    Person * person = [Person new];
    NSLog(@"%p",[Person class]);
    NSLog(@"%p",[person class]);
    NSLog(@"%p",object_getClass(person));
    NSLog(@"%p",object_getClass([person class]));
    

    打印结果:

    2017-07-07 10:22:11.228 Total[4157:410438] 0x10ccf2740
    2017-07-07 10:22:11.228 Total[4157:410438] 0x10ccf2740
    2017-07-07 10:22:11.229 Total[4157:410438] 0x10ccf2740
    2017-07-07 10:22:11.229 Total[4157:410438] 0x10ccf2718
    

    为什么打印的结果不一样呢?这就要分析类和对象了。

    Runtime数据结构

    在Objective-C中,使用[receiver message]语法并不会马上执行receiver对象的message方法的代码,而是向receiver发送一条message消息,这条消息可能由receiver来处理,也可能由转发给其他对象来处理,也有可能假装没有接收到这条消息而没有处理。其实[receiver message]被编译器转化为:

    idobjc_msgSend (idself, SEL op, ... );
    

    这个可以在objc/message.h中找到。
    下面从两个数据结构id和SEL来逐步分析和理解Runtime有哪些重要的数据结构。

    SEL

    SEL是函数objc_msgSend第二个参数的数据类型,表示方法选择器,按下面路径打开objc.h文件

    SEL数据结构

    typedef struct objc_selector *SEL;
    其实它就是映射到方法的C字符串,你可以通过Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。

    id

    接下来看objc_msgSend第一个参数的数据类型id,id是通用类型指针,能够表示任何对象。按下面路径打开objc.h文件


    id数据结构

    /// Represents an instance of a class.
    struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类
    注意:根据Apple的官方文档Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技术实现的,isa指针在运行时被修改,指向一个中间类而不是真正的类。所以,你不应该使用isa指针来确定类的关系,而是使用class方法来确定实例对象的类。

    Class

    isa指针的数据类型是Class,Class表示对象所属的类,按下面路径打开objc.h文件


    Class数据结构

    查看到objc_class结构体定义如下:

    struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                        OBJC2_UNAVAILABLE;
    long version                                            OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                      OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                            OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                    OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;
    

    注意:OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.0的遗留版本,但我们仍能从中获取一些有用信息。
    让我们分析一些重要的成员变量表示什么意思和对应使用哪些数据结构。
    isa表示一个Class对象的Class,也就是Meta Class(元类)。在面向对象的设计中,一切都是对象,Class在设计中本身也是一个对象。我们会在objc-runtime-new.h文件找到证据,发现objc-class有一下定义:

    struct objc_class : objc_object {// Class ISA;Class
    superclass;cache_tcache;// formerly cache pointer and
    vtableclass_data_bits_tbits;// class_rw_t * plus custom rr/alloc
    flags......}
    

    由此可见,结构体objc_class也是继承objc_object,说明Class在设计中本身也是一个对象
    其实Meta Class也是一个Class,那么它也跟其他Class一样有自己的isa和super_class指针,关系如下:

    Class isa and superclass relationship from Google

    上图实线是super_class指针,虚线是isa指针。有几个关键点需要解释以下:
    Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
    每个Class都有一个isa指针指向唯一的Meta class
    Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。
    每个Meta class的isa指针都指向Root class (meta)。

    super_class

    表示实例对象对应的父类

    name

    表示类名

    ivars

    表示多个成员变量,它指向objc_ivar_list结构体。在runtime.h可以看到它的定义:

    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;
    

    objc_ivar_list其实就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的单个成员变量信息。

    methodLists

    表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改methodLists的值来添加成员方法,也是Category实现原理,同样也解释Category不能添加实例变量*的原因。在runtime.h可以看到它的定义:

    struct objc_method_list {
    struct objc_method_list *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_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息。

    cache

    顾名思义,Cache主要用来缓存,那它缓存什么呢?我们先在runtime.h文件看看它的定义:

    typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
    
    #define CACHE_BUCKET_NAME(B)  ((B)->method_name)
    #define CACHE_BUCKET_IMP(B)   ((B)->method_imp)
    #define CACHE_BUCKET_VALID(B) (B)
    #ifndef __LP64__
    #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
    #else
    #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
    #endif
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method buckets[1]                                        OBJC2_UNAVAILABLE;
    };
    

    Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

    protocols

    表示类遵循哪些协议

    Method

    Method表示类中的某个方法,在runtime.h文件中找到它的定义:

    /// An opaque type that represents a method in a class definition.
    struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                      OBJC2_UNAVAILABLE;
    IMP method_imp                                          OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    

    其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针,后面会重点提及。

    Ivar

    Ivar表示类中的实例变量,在runtime.h文件中找到它的定义:

    struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
    #endif
    }                                                            OBJC2_UNAVAILABLE;
    

    Ivar其实就是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。

    IMP

    在上面讲Method时就说过,IMP本质上就是一个函数指针,指向方法的实现,在objc.h找到它的定义:

    /// A pointer to the function of a method implementation.
    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ );
    #else
    typedef id (*IMP)(id, SEL, ...);
    #endif
    

    当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

    消息发送

    前面从objc_msgSend作为入口,逐步深入分析Runtime的数据结构,了解每个数据结构的作用和它们之间关系后,我们正式转入消息发送这个正题。

    objc_msgSend函数

    在前面已经提过,当某个对象使用语法[receiver message]来调用某个方法时,其实[receiver message]被编译器转化为:

    id objc_msgSend (idself, SEL op, ... );
    

    现在让我们看一下objc_msgSend它具体是如何发送消息:

    首先根据receiver对象的isa指针获取它对应的class

    优先在class的cache查找message方法,如果找不到,再到methodLists查找

    如果没有在class找到,再到super_class查找

    一旦找到message这个方法,就执行它实现的IMP。

    Objc Message

    self与super

    为了让大家更好地理解self和super,借用sunnyxx博客的ios程序员6级考试一道题目:下面的代码分别输出什么?

    @implementation Son : Father
    - (id)init {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    @end
    

    self表示当前这个类的对象,而super是一个编译器标示符,和self指向同一个消息接受者。在本例中,无论是[self class]还是[super class],接受消息者都是Son对象,但super与self不同的是,self调用class方法时,是在子类Son中查找方法,而super调用class方法时,是在父类Father中查找方法。
    当调用[self class]方法时,会转化为objc_msgSend函数,这个函数定义如下:

    id objc_msgSend(idself, SEL op, ...)
    

    这时会从当前Son类的方法列表中查找,如果没有,就到Father类查找,还是没有,最后在NSObject类查找到。我们可以从NSObject.mm文件中看到- (Class)class的实现:

    - (Class)class{
            return object_getClass(self);
    }
    

    所以NSLog(@"%@", NSStringFromClass([self class]));会输出Son。
    当调用[super class]方法时,会转化为objc_msgSendSuper,这个函数定义如下:

    id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
    

    objc_msgSendSuper函数第一个参数super的数据类型是一个指向objc_super的结构体,从message.h文件中查看它的定义:

    /// Specifies the superclass of an instance. 
    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained 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 Class class;
    #else
        __unsafe_unretained Class super_class;
    #endif
        /* super_class is the first class to search */
    };
    #endif
    

    结构体包含两个成员,第一个是receiver,表示某个类的实例。第二个是super_class表示当前类的父类。

    这时首先会构造出objc_super结构体,这个结构体第一个成员是self,第二个成员是(id)class_getSuperclass(objc_getClass("Son")),实际上该函数会输出Father。然后在Father类查找class方法,查找不到,最后在NSObject查到。此时,内部使用objc_msgSend(objc_super->receiver, @selector(class))去调用,与[self class]调用相同,所以结果还是Son。

    未完待续

    相关文章

      网友评论

          本文标题:iOS runtime

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