理解类与对象的本质对于掌握一门语言是至关重要的,本文将从结构类型的角度探讨OC的类对象、实例对象、元类对象(Meta Class)。
我们先看一张图:
- 每个 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的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。
由此可见,在给实例对象或类对象发送消息时,寻找方法列表的规则为:
*当发送消息给实例对象时,消息是在寻找这个对象的类的方法列表(实例方法)
*当发送消息给类对象时,消息是在寻找这个类的元类的方法列表(类方法)
所有元类都有一个根元类,比如所有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指向父元类,若根元类则指向该根类
参考文章
1.[格物致知iOS类与对象] https://mp.weixin.qq.com/s/iBELEyUfnShnLhS5xJh4mQ
2.[清晰理解Objective-C元类]http://blog.csdn.net/beclosedtomyheart/article/details/50164353
3.[Objective-C Runtime 运行时之一:类与对象]http://southpeak.github.io/2014/10/25/objective-c-runtime-1/