OC类对象/实例对象/元类解析

上下文- 前情概述:

上一篇文章 OC对象占用内存原理 (一文彻底搞懂) 中我们讲到 OC 对象本身是一个结构体, 这个结构体只有一个 isa 指针, 关于 OC 对象实际开辟内存原理我们搞清楚了. 那么本篇文章就结合 runtime 来实际探讨一下 OC 对象方法查找的具体流程以及方法调用的原理.

isa

任何数据结构,只要在恰当的位置具有一个指针指向一个 class,那么,它都可以被认为是一个对象。objc_classobjc_objectstruct 指针都能称为对象,我们为了区分可以把它们分别叫做类对象和实例对象,其中实例对象的 isa 指针指向类对象 (这个指向下面会讲)。

OC 中,一个对象所属于哪个类,是由它的 isa 指针指向的。这个 isa 指针指向这个对象所属的 class

个人建议: 可以适当结合英文意思来理解, isa -> is a what? -> 你是个啥.

实例对象 | 类对象 | 元类 的基础概念区分

简单来个 :
提前创建了一个类 LBPerson 继承与 NSObject. 然后创建这个对象.

LBPerson * obj = [[LBPerson alloc] init];
  • 实例对象 : obj, 其 isa 指向类对象
  • 类对象 : LBPerson , 其 isa 指向元类
  • 元类 : 元类就是类对象所属的类 , 其 isa 指向根源类, 也就是 NSObject 的元类.


如果我们在创建一个子类,继承与 LBPerson , 那么这个子类就是上图中的 Subclass(class) , 实例化出来的对象就是 Instance Of Subclass.

需要注意的是:

  • 任何元类的 isa 都指向根元类. 根源类的 isa 指向它自己. 以此来实现一个完整的内循环, 所有的类,都会有类型, isa 不会为空.
  • NSObject 的父类为 nil .
  • 根元类的父类指向 NSObject .

那么这三种类型的对象分别都持有什么呢? 比如说 方法到底存在哪个对象中? 属性存在哪个对象? 接下来我们一一探讨.

类对象

类对象我们通过源码来查看其中保存了哪些内容,
shift+ cmd + O, 输入 objc.


找到 38 行:

typedef struct objc_class *Class;

cmd + 点 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;

稍微介绍一下各个属性的意思:

  • isa :上文讲述过
  • super_class :父类,如果该类已经是最顶层的根类,那么它为 nil
  • version :类的版本信息,默认为 0
  • info :供运行期使用的一些位标识。
  • instance_size :该类的实例变量大小
  • ivars :成员变量的列表
  • methodLists :方法列表
  • cache :缓存结构体桶
  • protocols :协议列表

类对象总结:
是一个功能完整的对象。特殊之处在于它们是在运行时由编译器创建的,它没有自己的实例变量(这里区别于类的成员变量,他们是属于实例对象的,而不是属于类对象的,类方法是属于类对象自己的),但 类对象中存着成员变量与实例方法列表

存储内容如图:


实例对象

先来看控制台提供给我们的.


我们看到实例对象中有属性, 和 isa. 这也跟我们上一篇博客中探讨 OC 对象占用内存原理 得出的结果是一模一样.

实例对象总结:
当我们在代码中新创建一个实例对象时,拷贝了实例所属的类的成员变量,但不拷贝类定义的方法。调用实例方法时,根据实例的 isa 指针去寻找方法对应的函数指针。

存储内容如图:


元类

OC 的类方法是使用元类的根本原因, 而这也是元类中存储的最重要的内容. 当你给类发消息时,消息是在寻找这个类的元类的方法列表. 类对象是元类的实例. 因此元类存储着类方法.

存储内容如下:


元类对象总结:
OC 的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。其他时候都倾向于隐藏元类,因此真实世界没有人发送消息给元类对象。元类的定义和创建看起来都是编译器自动完成的,无需人为干涉。

要获取一个类的元类,可使用如下定义的函数:

Class objc_getMetaClass(const char* name); //name为类的名字

此外还有一个获取对象所属的类的函数:

Class object_getClass(id obj) ;

由于类对象是元类的实例,所以当传入的参数为类名时,返回的就是指向该类所属的元类的指针。

总结

基于此, 我们可以根据其存储内容稍微总结一下

  • 1️⃣: 实例对象的 isa 指向类对象,当调用对象方法,通过实例对象的 isa 找到类对象,最终找到对对象方法进行调用

  • 2️⃣: 类对象的 isa 指向元类,调用类方法,通过类对象中的 isa 找到元类,最终找到元类中的类方法进行调用

  • 3️⃣: 当子类的对象要调用父类的对象方法,先通过子类的 isa 找到父类的 class. 然后通过 superClass 找到父类的 class 最后找到消息进行调用.


最后要提醒注意的一点是:

根类不一定是 NSObject,因为后面介绍的 objc_allocateClassPair 函数也可以创建出一个根类。

元类的实际应用

类对象和元类对象的相关方法:

  • ① : object_getClass 跟随实例的 isa 指针,返回此实例所属的类,对于实例对象 (instance) 返回的是类 (class) ,对于类 (class) 则返回的是元类 (metaclass) .

  • ② : class 方法对于实例对象 (instance) 会返回类 (class) , 但对于类 (class) 则不会返回元类 (metaclass), 而只会返回类本身,即[@"instance" class] 返回的是 __NSCFConstantString , 而 [NSString class] 返回的是 NSString

  • ③ : class_isMetaClass 可判断某类是否为元类.

  • ④ : 使用 objc_allocateClassPair 可在运行时创建新的类与元类对,使用 class_addMethodclass_addIvar 可向类中增加方法和实例变量,最后使用 objc_registerClassPair 注册后,就可以使用此类了。这体现了 OC 作为运行时语言的强大之一:在代码运行中动态创建类并添加方法。

至此, 三种不同的类的基础我们已经讲完, 下一篇会基于此基础, 探索方法查找流程.

你可能感兴趣的:(OC类对象/实例对象/元类解析)