runtime(零) Objc 中类和对象的本质

Posted by Abin's blog on April 8, 2016

Objc 中任何对象都可以称之为 id 类型,那么看下在 objc.hid 类型的定义:

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

注释中的描述是 一个指向类的实例的指针,那么是不是意味一个类的实例即对象就是一个 objc_object 结构体呢?再看源码:

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

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

通过阅读 runtime 的源码可以得出以下结构:

obj-class-isa

注:class_ro_t 中存放的是编译时可以确定的属性、方法和协议等

  1. Objc 中的对象是一个 objc_object 结构体,结构体中第一个变量是 isa_t ,存放着该对象所属类的信息;
  2. 类是一个objc_class 结构体,继承自 objc_object ,所以类也是一个对象,另外还有两个变量进行方法缓存和数据存放,比如变量、方法(实例方法,以 - 开头的方法)和所遵守的协议。
  3. 类的 isa 变量中存放的类是元类(meta-class),类是一个对象,对象的类型就是元类,元类存放着类的方法(类方法,以 + 开头的方法)。
  4. 元类也是类,所以元类也是对象。元类 isa 变量中存放的类是根元类(一般是 NSObject )。
  5. 根元类 isa 指向其自身
  6. 类和元类都是单例

做个试验,新建一个 BBObject 类,添加一个属性 bb_name,头文件如下:

@interface BBObject : NSObject

@property (nonatomic, strong) NSString *bb_name;

@end

那么以下代码会输出什么呢:

NSString *name = @"这是 bb_name";
void *cls = (__bridge void *)([BBObject class]);
void *bb_obj = &cls;
NSLog(@"%@",[(__bridge BBObject*)bb_obj bb_name]);

输出的是:2017-12-16 19:55:33.477716+0800 runtime[45876:7116185] 这是 bb_name

因为 Objc 中一个完整的对象就是 首地址指向一个类的连续空间,为什么是连续空间?那是因为对象还有自己属性变量的值要存储,这也是为什么没有给 bb_objbb_name 属性赋值,却打印出 name 值的原因,在 iOS 中,栈的地址是由高到低,堆的地址是由低到高,在这段代码中栈中依次压入了 namebb_obj,而 bb_obj 对象自身的属性是根据自身首地址进行偏移去获取,所以会取到 name 的值。

objc_iva

使用 clang -rewrite-objc BBObject.m 可以把得到重写后的 C++ 文件,在其中也可以看到其中获取属性就是自身地址加偏移量:

static NSString * _I_BBObject_bb_name(BBObject * self, SEL _cmd) { 
    return (*(NSString **)((char *)self + OBJC_IVAR_$_BBObject$_bb_name)); 
}