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

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

Objective-C对象模型

我们打开文件可以看到如下对NSObject的定义:

@interface 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) 中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以,为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。如下图:


每日一问08——runtime类与对象_第1张图片
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 指针就是这个结构体的第一个成员变量,而类的其它成员变量依次排列在结构体中。


每日一问08——runtime类与对象_第2张图片
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类与对象)