美文网首页
每日一问08——runtime类与对象

每日一问08——runtime类与对象

作者: 巫师学徒 | 来源:发表于2017-09-06 17:27 被阅读18次

    前几天看到老哥说他去面试,面试官问他OC里类对象和实例对象有什么区别?不知道Objective-C对象模型的同学很可能搞不清楚面试官究竟想问什么。
    本篇先讲对象模型到底是什么样的以及runtime中,类是如何实现的。

    Objective-C对象模型

    我们打开<objc/objc.h>文件可以看到如下对NSObject的定义:

    @interface NSObject <NSObject> {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
    

    可以看到NSObject 就是一个包含 isa 指针的结构体,在 Objective-C 语言中,每一个类实际上也是一个对象。每一个类也有一个名为 isa 的指针。每一个类也可以接受消息,例如[NSObject alloc],就是向 NSObject 这个类发送名为alloc消息。

    再看一下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;
    

    发现他里面也有一个isa指针,所以类也是一个对象,那它也必须是另一个类的实列,这个类就是元类 (metaclass)。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。

    1.isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),我们会在后面介绍它。

    2.super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

    3.cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。

    4.version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。

    元类 (metaclass)

    元类 (metaclass) 也是一个对象,那么元类的 isa 指针又指向哪里呢?为了设计上的完整,所有的元类的 isa 指针都会指向一个根元类 (root metaclass)。根元类 (root metaclass) 本身的 isa 指针指向自己,这样就行成了一个闭环。

    我们再来看看继承关系,由于类方法的定义是保存在元类 (metaclass) 中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以,为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。如下图:


    class-diagram.jpg

    图中的Root Class 是指 NSObject,我们可以从图中看出:

    1.NSObject 类包括它的对象实例方法。
    2.NSObject 的元类包括它的类方法,例如 alloc 方法。
    3.NSObject 的元类继承自 NSObject 类。
    4.一个 NSObject 的类中的方法同时也会被 NSObject 的子类在查找方法时找到

    为了验证上面说的内容,我做了以下几个测试,在这之前需要先了解几个函数具体的作用与实现方式。

    +class 与 objc_getClass()

    1.调用+class方法是无法获取meta-class,它只是返回类本身。

    2.对类对象调用objc_getClass()可以获取它的meta-class。

    -isKindOfClass和isMemberOfClass
    + (BOOL) isKindOfClass:(Class)class
    {
       Class r0 = object_getClass(self);
       while (1) {
           if (r0 == 0) {
               return 0;
           }else{
               if (r0 != class) {
                   r0 = [r0 superclass];
               }else{
                   return 1;
               }
           }
       }
    }
    
    + (BOOL)isMemberOfClass:(Class)class
    {
       return isa == (Class)aClass;
    }
    

    它们的内部实现大概是这个样子。可以看出:
    isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员.
    isMemberOfClass:确定一个对象是否是当前类的成员

    验证例子1
    BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
    BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
    

    输出结果:YES/NO/NO/NO。
    1.[NSObject class]获取的NSObject类对象 在函数体内获取的是它的元类,而NSObject的元类的父类就是NSObject本身,所以第一个是YES。
    2.因为是isMemberOfClass,所以元类不等于自己,返回NO
    3.同上,获得的是元类,这里元类的父类与类对象不相等。
    4.同上。

    类的成员变量

    如果把类的实例看成一个 C 语言的结构体(struct),上面说的 isa 指针就是这个结构体的第一个成员变量,而类的其它成员变量依次排列在结构体中。


    class-member.jpg

    验证例子2

    @interface NSObject (test)
    + (void)foo;
    - (void)foo
    @end
    @implementation NSObject (test)
    - (void)foo {
        NSLog(@"test result");
    }
    @end
    // 测试代码
    [NSObject foo];
    [[NSObject new] foo];
    

    测试结果:都正常的运行了-foo方法。
    原因是NSObject的类方法保存在根元类中,根元类没有这个方法便向它的父类寻找,根元类的父类指向NSObject本身,而成员方法是保存在类中的,所以找到了这个方法。

    可变与不可变

    因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时动态给对象增加成员变量。
    相对的,对象的方法定义都保存在类的可变区域中。我们可以看到方法的定义列表是一个名为 methodLists的指针的指针(如下图所示)。通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。

    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;
    

    Category

    Category是表示一个指向分类的结构体的指针,其定义如下:

    typedef struct objc_category *Category
    struct objc_category{
         char *category_name                         OBJC2_UNAVAILABLE; // 分类名
         char *class_name                            OBJC2_UNAVAILABLE;  // 分类所属的类名
         struct objc_method_list *instance_methods   OBJC2_UNAVAILABLE;  // 实例方法列表
         struct objc_method_list *class_methods      OBJC2_UNAVAILABLE; // 类方法列表
         struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE; // 分类所实现的协议列表
    }
    

    这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods列表是objc_class中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。
    可发现,类别中没有ivar成员变量指针,也就意味着:类别中不能够添加实例变量和属性

    小结:
    1.实例对象是类的对象,类对象是元类的对象。
    2.类对象和元类对象有着同样的继承关系
    3.成员方法保存在类中,类方法保存在元类中。

    1. Category包含对象方法列表和元类方法列表,但没有ivar成员变量指针。所以只能添加方法不能添加变量和属性。
      5.调用类方法时,是先查找元类本身是否有该方法,如果没有,则元类向它的父类查找,而不是类对象的父类。

    相关文章

    神经病院objc runtime入院考试
    Objective-C对象模型及应用
    Objective-C Runtime 运行时之一:类与对象
    初识 Objective-C Runtime
    Runtime之类与对象总结

    相关文章

      网友评论

          本文标题:每日一问08——runtime类与对象

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