0%

Objective-C Runtime 之二 理解类和对象

理解常用数据结构

观察 NSObject 类的结构:

1
2
3
4
5
6
7
8
9
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

可以看到,NSObject 类遵守同名的 NSObject 协议,其类的内部拥有唯一的成员变量 isaisaClass 类型。

objc_class

1
2
3
// Object.mm

typedef struct objc_class *Class;

Class 就是 ObjC 中类的实现,本质上是一个结构体指针,这个结构体是 objc_class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// objc-runtime-new.h

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 // 类信息,包括变量、属性、方法等

class_rw_t *data() const {
return bits.data();
} // 把类信息中的可读写部分暴露出来

// ...
};

上述代码总结如下内容:

  • objc_class 继承自 objc_object,也就是说,类本质上也是一个对象,称为类对象。
  • Class superclass 类对象拥有继承体系。
  • cache_t cache 为方法缓存,用于快速查找。
  • class_data_bits_t 存储的类的信息。

cache_t

cache_t 为方法缓存,用于快速查找方法的执行函数。常规的方法调用,需要通过 isa 先去其类对象的方法列表中查找,找不到就去查找父类,依次查找到 NSObject 类,如果一些方法经常被调用,这样的方式效率较低,所以就设置一个方法缓存,当某个方法被调用过,就将其放入方法缓存列表,之后就优先查找方法缓存。

cache_t 是一个可增量扩展的哈希表结构,使用哈希表实现可以提高查找效率。

cache_t 可以简单理解为由一组 bucket_t 结构组成,后者的主要构成是 SELIMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// objc-runtime-new.h

struct cache_t {

// ...
public:
static bucket_t *emptyBuckets();

struct bucket_t *buckets();
mask_t mask();
mask_t occupied();

// ...
};

bucket_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// objc-runtime-new.h

struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp; // 无类型的函数指针,对应具体的函数实现
explicit_atomic<SEL> _sel; // 方法选择器
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif

// ...
};

通过 SEL 可以找到对应的 IMP

class_data_bits_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// objc-runtime-new.h

struct class_data_bits_t {
friend objc_class;

// Values are the FAST_ flags above.
uintptr_t bits;

// ...

public:

class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
} // data,存储可动态改变的部分,内部还包含只读的部分

// ...

// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro;
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
} // 将只读部分暴露出来

// ...
};

class_data_bits_t 主要是对 class_rw_t 的封装。

class_rw_t

class_rw_t 存储类相关的读写信息,还包含 class_ro_t

我们通常为类动态添加的方法、属性、协议都是在这里进行实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// objc-runtime-new.h

struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t version;
uint16_t witness;

const class_ro_t *ro; // 编译就确定,不能动态修改

method_array_t methods; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表

Class firstSubclass;
Class nextSiblingClass;

char *demangledName;

// ...
};

主要包含:

  • const class_ro_t *ro
  • method_array_t methods,方法列表
  • property_array_t properties,属性列表
  • protocol_array_t protocols,协议列表

其中 method_array_tproperty_array_tprotocol_array_t 都是二维数组结构。

如果一个分类,分类中有若干方法,这一组方法就组成一个数组,然后这个数组作为 methods 的其中一个元素。

所以 methods 最终的组成元素就是一个个具体的方法,是 method_t 结构,属性和协议也类似。

class_ro_t

class_ro_t 代表了类的相关只读信息,这些内容在编译时就确定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// objc-runtime-new.h

struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif

const uint8_t * ivarLayout;

const char * name; // 类名
method_list_t * baseMethodList; // 方法列表
protocol_list_t * baseProtocols; // 协议列表
const ivar_list_t * ivars; // 变量列表

const uint8_t * weakIvarLayout;
property_list_t *baseProperties; // 属性列表

// ...

method_list_t *baseMethods() const {
return baseMethodList;
}

// ...
};

主要包含:

  • const char * name,类名
  • method_list_t * baseMethodList,方法列表
  • protocol_list_t * baseProtocols,协议列表
  • const ivar_list_t * ivars,变量列表
  • property_list_t *baseProperties,属性列表

这里的列表都是一维数组,代表本类中最初设置的方法、属性等。

method_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// objc-runtime-new.h

struct method_t {
SEL name; // 方法名
const char *types; // 方法类型
MethodListIMP imp; // 方法实现地址

struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};

函数四要素有:函数名、返回值、参数、函数体。

method_t 中主要包含:

  • SEL name,包含方法名和参数个数信息
  • const char *types,包含函数返回值和参数类型
  • MethodListIMP imp,方法实现地址

关于 types,需要参考 Type Encodings,一个方法的返回值以及参数类型,苹果定义了不同的字符来进行表示,其中第一个是返回值类型,后续依次接参数。

-(void)aMethod 方法,types 存储应该是 v@:,通过查表,三个字符分布代表 voididSEL,也就是说这个方法的返回值是 void,第一个参数是消息接受者,第二个参数是 SEL

protocol_t

1
2
3
4
5
6
7
8
9
10
11
12
13
// objc-runtime-new.h

struct protocol_t : objc_object { // 协议也是一个对象
const char *mangledName; // 协议名
struct protocol_list_t *protocols; // 协议列表
method_list_t *instanceMethods; // 实例方法
method_list_t *classMethods; // 类方法
method_list_t *optionalInstanceMethods; // 可选实例方法
method_list_t *optionalClassMethods; // 可选类方法
property_list_t *instanceProperties; // 实例属性

// ...
};

ivar_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// objc-runtime-new.h

struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms.
// We read and write only 32 bits of it.
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values.
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit.
#endif
int32_t *offset; // 地址的偏移
const char *name; // 变量名
const char *type; // 变量类型
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;

uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};

property_t

1
2
3
4
5
6
// objc-runtime-new.h

struct property_t {
const char *name; // 属性名
const char *attributes; // 属性修饰符
};

objc_object

ObjC 中的 id 类型本质上就是一个 objc_object 的结构体指针,objc_object 结构体其实就是 ObjC 中对对象的实现。objc_class 继承自 objc_object,就是说,类也是对象。

1
2
3
Object.mm

typedef struct objc_object *id;
1
2
3
4
5
6
7
8
9
10
11
12
// objc-private.h

struct objc_object {
private:
isa_t isa;

// 关于 isa 的一些相关操作,如通过 isa 获取类对象、元类对象
// 弱引用相关方法
// 关联对象相关方法
// 内存管理相关方法
// ...
};

isa

objc_object 中存在一个成员变量 isa,拥有 isa 在 ObjC 中就是对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// objc-private.h

union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

isa 是一个联合体,在 64 位机器上这个共用体就是 64 位的(32 位下就是 32 位的)。

isa 可能是指针型的,也可能是非指针型的。
指针型就是说这个联合体的内容存储的就是一个指针,比如一个对象的 isa 指针找到对象的类对象。
非指针型的是说,这个联合体中的部分位代表地址,其他位表示一些其他信息。因为 64 位用于存储地址是多余的,只需要部分位已经能满足寻址需要,那么其他剩余的位置就可以存储一些别的。

isa 不总是指向实例所属的类,比如 KVO 就是将被观察对象的 isa 指向一个中间类。确定实例所属的类应该使用 class 方法。

1
2
3
4
5
6
7
8
9
10
11
12
// isa.h

# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19

对象模型

除了一个特殊类 NSProxyNSObject 类是所有类最终的父类(包括 NSObject 元类),NSObject 元类是所有类的元类(包括它自己)。

对象方法是存储在类对象的方法列表中,类方法是存储在元类对象的方法列表中。

图 1 - 对象模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
NSLog(@"Person实例地址:%@", self);

Class personClass = objc_getClass("Person");
NSLog(@"Person类对象地址:%p", personClass);

Class personMetaClass = objc_getMetaClass("Person");
NSLog(@"Person元类对象地址:%p", personMetaClass);

Class personSuperClass = class_getSuperclass(personClass);
NSLog(@"Person父类类对象地址:%p", personSuperClass);

NSObject *obj = [NSObject new];
NSLog(@"NSObject实例地址:%@", obj);

Class objectClass = objc_getClass("NSObject");
NSLog(@"NSObject类对象地址:%p", objectClass);

Class objectMetaClass = objc_getMetaClass("NSObject");
NSLog(@"NSObject元类对象地址:%p", objectMetaClass);

Class objectSuperClass = class_getSuperclass(objectClass);
NSLog(@"NSObject父类类对象地址:%p", objectSuperClass);

NSLog(@"Person实例对象的isa:%p", object_getClass(self));
NSLog(@"Person类对象的isa:%p", object_getClass(personClass));
NSLog(@"Person元类对象的isa:%p", object_getClass(personMetaClass));

NSLog(@"NSObject实例对象的isa:%p", object_getClass(obj));
NSLog(@"NSObject类对象的isa:%p", object_getClass(objectClass));
NSLog(@"NSObject元类对象的isa:%p", object_getClass(objectMetaClass));

/*
Person实例地址:<Person: 0x1005b5ef0>
Person类对象地址:0x100003208
Person元类对象地址:0x1000031e0
Person父类类对象地址:0x7fff970c7118

NSObject实例地址:<NSObject: 0x103804920>
NSObject类对象地址:0x7fff970c7118
NSObject元类对象地址:0x7fff970c70f0
NSObject父类类对象地址:0x0

Person实例对象的isa:0x100003208
Person类对象的isa:0x1000031e0
Person元类对象的isa:0x7fff970c70f0

NSObject实例对象的isa:0x7fff970c7118
NSObject类对象的isa:0x7fff970c70f0
NSObject元类对象的isa:0x7fff970c70f0
*/

关于对象模型的若干知识点

调用类方法最终却调用了实例方法

由于 NSObject 元类对象父类是 NSObject 类对象,所以如果调用 NSObject 类方法没有对应实现,但是 NSObject 存在同名的对象方法时,会最终调用到该对象方法。

super 关键字的实质

super 不是一个指针,而是一个关键字,使用 super 的含义就是查找方法时,不在本类的类对象方法列表中查找,而是跳过他,直接从父类的类对象方法列表中开始进行查找。但是消息接受者都是实例本身。

如下面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation Person

@end

@interface Student : Person

@end

@implementation Student

- (instancetype)init {
self = [super init];

if (self) {

NSLog(@"%@", NSStringFromClass([self class])); // Student
NSLog(@"%@", NSStringFromClass([super class])); // Student
}

return self;
}

@end

最终两个打印结果都是 Student,原因是两者的方法接受者都是同一个实例,只不过查找方法时,一个是从本类的类对象开始,一个是从父类的类对象开始,但由于 class 方法是 NSObject 中的方法,最终都是寻找到根类然后执行,所以两者其实不管是调用者还是最终调用到的函数都是相同的。

[self class] 本质上是调用 objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)[super class] 本质上是调用 objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)。其中 objc_super 结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver; // 还是调用者本身

/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};

其中的 receiver 就是调用者本身。