理解类与对象的本质对于掌握一门语言是至关重要的,本文将从结构类型的角度探讨OC的类对象、实例对象、元类对象(Meta Class)。

我们先看一张图:

 

 

iOS类、实例、元类关系图.jpg

  • 每个 Class 都有一个 isa 指针指向一个唯一的 Meta Class
  • 每一个 Meta Class 的 isa 指针都指向最上层的 Meta Class,即 NSObject 的 MetaClass,而最上层的 MetaClass 的 isa 指针又指向自己

1.类对象

类对象是由程序员定义并在运行时由编译器创建的,它没有自己的实例变量,这里需要注意的是类的成员变量和实例方法列表是属于实例对象的,但其存储于类对象当中的。我们在objc.h下看看Class的定义:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可见,Class是指向C的结构体objc_class的指针,我们再看一下objc_class的定义(runtime.h):

struct objc_class {
    Class _Nonnull isa;                                     // 指向所属类的指针(_Nonnull)
    Class _Nullable super_class;                      // 父类  
    const char * _Nonnull name;                      // 类名(_Nonnull)
    long version;                                              // 类的版本信息(默认为0)   
    long info;                                                   // 类信息(供运行期使用的一些位标识)   
    long instance_size;                                    // 该类的实例变量大小
    struct objc_ivar_list * _Nullable ivars;        // 该类的成员变量链表
    struct objc_method_list * _Nullable * _Nullable methodLists;  // 方法定义的链表                  
    struct objc_cache * _Nonnull cache;                        // 方法缓存
    struct objc_protocol_list * _Nullable protocols;        // 协议链表
} ;
  • isa指针是和Class同类型的objc_class结构指针,类对象的指针指向其所属的类,即元类。元类中存储着类对象的类方法,当访问某个类的类方法时会通过该isa指针从元类中寻找方法对应的函数指针。
  • super_class为该类所继承的父类对象,如果该类已经是最顶层的根类(如NSObject或NSProxy), 则 super_class为NULL。
  • ivars是一个指向objc_ivar_list类型的指针,用来存储每一个实例变量的地址。
  • info为运行期使用的一些位标识,比如:CLS_CLASS (0x1L)表示该类为普通类, CLS_META (0x2L)则表示该类为元类。
  • methodLists用来存放方法列表,根据info中的标识信息,当该类为普通类时,存储的方法为实例方法;如果是元类则存储的类方法。
  • cache用于缓存最近使用的方法。系统在调用方法时会先去cache中查找,在没有查找到时才会去methodLists中遍历获取需要的方法。

2.实例对象

实例对象是我们对类对象alloc或者new操作时所创建的,在这个过程中会拷贝实例所属类的成员变量,但并不拷贝类定义的方法。调用实例方法时,系统会根据实例的isa指针去类的方法列表及父类的方法列表中寻找与消息对应的selector指向的方法。同样的,我们也来看下其定义(objc.h):

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

objc_object这个结构体只有一个isa变量,指向实例对象所属的类。任何带有以指针开始并指向类结构的结构都可以被视作objc_object, 对象最重要的特点是可以给其发送消息. NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。

/// A pointer to an instance of a class.
typedef struct objc_object *id;

id是一个objc_object结构类型的指针。该类型的对象可以转换为任何一种对象,类似于C语言中void *指针类型的作用(objc.h)。

3.元类对象(MetaClass)

顾名思义,元类对象即是描述类对象的类,每个类都有自己的元类,也就是结构体objc_class中isa指向的类。Objective-C的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。

 

 

类存储示意图.jpg

 

由此可见,在给实例对象或类对象发送消息时,寻找方法列表的规则为:

*当发送消息给实例对象时,消息是在寻找这个对象的类的方法列表(实例方法)

*当发送消息给类对象时,消息是在寻找这个类的元类的方法列表(类方法)

所有元类都有一个根元类,比如所有NSObject的子类的元类都会以NSObject的元类作为他们的类。所有的元类使用根元类作为他们的类,根元类的元类则就是它自己,也就是说根元类的isa指针指向他自己。

这里我们可以进一步研究一下官方技术文档runtime.h:

OBJC_EXPORT Class _Nullable
object_getClass(id _Nullable obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

这里object_getClass与NSObect.h中的类方法 +(Class)class;和实例方法+ (Class)class;有什么不同呢?请看源码:

Class object_getClass(id obj) {
    return _object_getClass(obj);
}

static inline Class _object_getClass(id obj) {

    #if SUPPORT_TAGGED_POINTERS

    if (OBJ_IS_TAGGED_PTR(obj)){
        uint8_t slotNumber = ((uint8_t)(uint64_t) obj) & 0x0F;
        Class isa = _objc_tagged_isa_table[slotNumber];
        return isa;
    }

    #endif

        if (obj) return obj->isa;   
        else return Nil;

}

_object_getClass函数就是返回对象的isa指针,也就是返回该对象所指向的所属类。

+ (Class)class {
    return self; // 返回自身指针
}

- (Class)class {
    return object_getClass(self); // 调用'object_getClass'返回isa指针
}

总结一下实例对象,类对象以及元类对象之间的isa指向和继承关系的规则为:
规则一: 实例对象的isa指向该类,类的isa指向元类(metaClass)
规则二: 类的superClass指向其父类,如果该类为根类则值为nil
规则三: 元类的isa指向根元类,如果该元类是根元类则指向自身
规则四: 元类的superClass指向父元类,若根元类则指向该根类

 

 

4、下面我们来看一个例子,方便更好的来理解元类的概念
同时我们首先理解几个知识点

object_getClass(实例对象) == [实例对象 class]
[类对象 class] == 类对象
object_getClass(类对象) == 类对象的isa == 元类
object_getClass(类对象) != [类对象 class]

添加方法,其实是在 类对象/实例对象 中的isa指针的类中添加

 

首先定义一个 Obj的类
----------------------------------------------------
@interface Obj : NSObject
//实例方法
- (void)print1;
//类对象方法
+ (void)classPrint;
@end
@implementation Obj
//这里不去实现它,后面我们会通过runtime的知识添加方法
@end
下面我们切换当控制器中 ----------------------------------------------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    
    Obj * obj = [Obj new];
    Class class11 = object_getClass(obj);
    Class class12 = [obj class]; //这个是获取当前的类的对象,实例是就是isa指向的类对象,但是类对象指向确是自己本身
    
    
    
    NSLog(@"class1 :%p,class2 = %p",class11,class12);
    //class1 :0x1077b8f00,class2 = 0x1077b8f00
    
    
    Class class21 = object_getClass(class11);//Son的Class的元类
    Class class22 = [class12 class];//还是本身Son的Class
    Class classMeta= objc_getMetaClass(object_getClassName(obj));//Son的Class元类
    
    NSLog(@"class1 :%p,class2 = %p,classMeta = %p",class21,class22,classMeta);
    //class1 :0x1077b8ed8,class2 = 0x1077b8f00,classMeta = 0x1077b8ed8
    //在给对象或者类添加方法的时候,其实是给isa 指向的类添加方法,就是说 一个普通的对象是给它的class添加方法,而 一个普通的类对象,需要添加方法其实是给它isa指向的 元类添加方法
    
    //给obj实例对象 添加 方法
    class_addMethod(class11, @selector(print1), (IMP)IMPFunc, NULL);
    [obj print1];
    
    //给obj的类对象添加方法
    class_addMethod(class21, @selector(classPrint), (IMP)IMPMetaClassFunc, NULL);
    [Obj classPrint];
    
}

void IMPFunc(id self ,SEL cmd) {
    
    NSLog(@"print1");
}
void IMPMetaClassFunc(id self ,SEL cmd) {
    
    NSLog(@"IMPMetaClassFunc");
}

 

 

参考文章

1.[格物致知iOS类与对象] https://mp.weixin.qq.com/s/iBELEyUfnShnLhS5xJh4mQ

2.[清晰理解Objective-C元类]

3.[Objective-C Runtime 运行时之一:类与对象]http://southpeak.github.io/2014/10/25/objective-c-runtime-1/