一个木匠

zqqf16 的个人博客

从源码看Objective-C的对象模型(一)

前言

刚接触Objective-C的时候,曾被一个bug折磨得很痛苦,后来才发现是我对Category理解错了,不知道它对所有该类的实例都会起作用。当时就决定要好好研究一下Objective-C的对象模型。正好最近手头的活忙完了,可以有时间做个总结。

本文所参照的代码是clang rewrite之后的C++代码,以及Apple开源的Runtime代码,地址在这, 也可以参考 Github 上的这个镜像: opensource-apple/objc4

基本概念

对于Objective-C有关对象、类、元类的基本概念,我就不细说了,和Python很像。这里有三篇文章,对我当时研究的启发很大,大家可以参考一下。

这里只做个总结:

  1. 所有对象都是一个结构体,它的第一个成员是一个指向objc_class类型的指针——isa。或者说所有第一个成员是objc_class指针类型的结构体都是对象。
  2. 实例对象的isa指向它的类对象,类对象的isa指向元类,元类的isa指向根元类,根元类的指向它自己。

实例、类、元类初探

新建一个NSObject的子类,如下:

/* FirstClass.h */
#import <Foundation/Foundation.h>

@interface FirstClass : NSObject
{
    NSString *className;
}
@end

/* FirstClass.m */
#import "FirstClass.h"

@implementation FirstClass

@end

然后执行clang -rewrite-objc FirstClass.m,这样就会在当前目录生成一个名叫FirstClass.cpp的文件,打开文件(注意,因为包含了Foundation.h,所以生成的cpp文件巨大,可以忽略前1W行)可以看到,FirstClass被改写成了这样:

typedef struct objc_object FirstClass;

成了objc_object类型的结构体。objc_object的声明如下:

struct objc_object {
    Class isa ;
};

也就是说,当我们用FirstClass *fc;这样的形式去声明一个变量的时候,其实这个变量就是一个指向objc_object类型的指针,也就是上面我总结的,他指向的是一个对象。

接着往下看,会发现以下这样的代码:

extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_FirstClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_NSObject,
    0, // &OBJC_METACLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_FirstClass,
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;

extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_FirstClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_FirstClass,
    0, // &OBJC_CLASS_$_NSObject,
    0, // (void *)&_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_FirstClass,
};

根据字面意思就可以看明白了,分别定义了两个变量,OBJC_METACLASS_$_FirstClassOBJC_CLASS_$_FirstClass,前者是元类,后者是类。他们都是_class_t类型的,都是对象。因为是自动改写的,所以变量名都有些奇怪,但并不影响阅读。

这里简单介绍一下__declspec(dllimport)__declspec(dllexport) 以及 __attribute__ ((used, section ("__DATA,__objc_data")))。前两个分别声明导入和导出函数,一般用于动态链接库,后面的是一个GNU C的扩展,used告诉编译器即使后面没有引用也要编译这段代码,section ("__DATA,__objc_data")是说把这段代码编译到__DATA,__objc_data段,而不是默认的代码段。至于为啥要这样,我也不太明白,有待进一步研究。

2015-1-21 更新

  1. "__DATA,__objc_data" 是要把这段数据放在 "__DATA" 段(Segment)的 "__objc_data" 节(Section)。这跟 Mach-O 文件格式的规定有关,类似的还有 "__DATA,__objc_classlist"、"__TEXT,__objc_methodname" 这样的。
  2. 这段代码定义了全局变量,应该放在数据段,而不是代码段

_class_t的声明如下:

struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};

基本可以当成runtime.h中的objc_class类型。

后面还有一段代码,定义了一个初始化函数:

static void OBJC_CLASS_SETUP_$_FirstClass(void ) {
    OBJC_METACLASS_$_FirstClass.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_FirstClass.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_FirstClass.cache = &_objc_empty_cache;
    OBJC_CLASS_$_FirstClass.isa = &OBJC_METACLASS_$_FirstClass;
    OBJC_CLASS_$_FirstClass.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_FirstClass.cache = &_objc_empty_cache;
}

可以看到元类的isasuperclass都指向了NSOBject,而类的isa指向了元类、superclass指向了NSObject。

看到这就明白了,类对象以及元类对象是编译时就会创建好的。那么实例对象是怎么创建的,实例变量是怎么保存的呢?且看下一篇分解~

--未完待续--