OC Runtime 学习笔记

isa

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。

从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。

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
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
// 摘自 objc4-723 Apple 开源 OC 源代码

nonpointer

0,代表普通的指针,存储着Class、Meta-Class对象的内存地址;

1,代表优化过,使用位域存储更多的信息。

has_assoc

是否有设置过关联对象,如果没有,释放时会更快。

has_cxx_dtor

是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快。

shiftcls

存储着Class、Meta-Class对象的内存地址信息。

magic

用于在调试时分辨对象是否未完成初始化。

weakly_referenced

是否有被弱引用指向过,如果没有,释放时会更快。

deallocating

对象是否正在释放。

extra_rc

里面存储的值是引用计数器减1。

has_sidetable_rc

引用计数器是否过大无法存储在isa中,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中。

Class 结构体

1
2
3
4
5
6
7
8
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(用于获取具体类信息)
};
// 摘自 objc4-723 Apple 开源 OC 源代码

class_rw_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bits & FAST_DATA_MASK 得到 class_rw_t 结构体实例
struct class_rw_t { // rw 代表 readwrite
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
// 摘自 objc4-723 Apple 开源 OC 源代码

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容

method_array_t 方法列表数据结构如下表格,数组中嵌套method_list_t数组,method_list_t数组中存着method_t,因此可以动态添加方法,我们还可以猜测一个分类中的方法相当于一个method_list_t数组。

method_list_t method_list_t method_list_t
method_t method_t method_t method_t
method_t method_t method_t method_t

class_ro_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// class_ro_t 存着类的原始信息
struct class_ro_t { // ro 代表 readonly
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance 对象占用的内存空间
#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;
};
// 摘自 objc4-723 Apple 开源 OC 源代码

method_list_t 数据结构如下表格

method_t
method_t
method_t

method_t

1
2
3
4
5
6
7
struct method_t {
SEL name; // 方法名
const char *types; // 编码(返回值类型,参数类型)
IMP imp; // 指向函数的指针(函数地址)
};
// 摘自 objc4-723 Apple 开源 OC 源代码

IMP代表函数的具体实现

1
2
/// A pointer to the function of a method implementation.
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);

SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似

  • 可以通过@selector()和sel_registerName()获得
  • 可以通过sel_getName()和NSStringFromSelector()转成字符串
  • 不同类中相同名字的方法,所对应的方法选择器是相同的
1
2
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

types

包含了函数返回值、参数编码的字符串

cache_t

Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

1
2
3
4
5
6
7
8
9
10
11
12
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; // 散列表的长度 - 1
mask_t _occupied; // 已缓存的方法数量
};
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
// 摘自 objc4-723 Apple 开源 OC 源代码

缓存查找

  • objc-cache.mm
  • bucket_t * cache_t::find(cache_key_t k, id receiver)

objc_msgSend

1
2
3
4
5
6
7
8
9
10
11
12
13
NSObject *object = [NSObject alloc];
object = [object init];
// objc_msgSend(object, sel_registerName("init"));
// 消息接收者(receiver):object
// 消息名称:init
[NSObject initialize];
// objc_msgSend(objc_getClass("NSObject"), sel_registerName("initialize"));
// 消息接收者(receiver):[NSObject class]
// 消息名称:initialize
// sel_registerName("init") = @selector(init);
// objc_getClass("NSObject") = [NSObject class];

OC的方法调用:消息机制,给方法调用者发送消息。所以OC中的方法调用,其实都是转换为objc_msgSend函数的调用。

objc_msgSend的执行流程可以分为3大阶段

  • 消息发送
  • 动态方法解析
  • 消息转发

如果三个阶段都没找不到合适的方法进行调用,会报错 unrecognized selector sent to instance

消息发送

动态方法解析

消息发送流程走完后没有找到对应方法的实现,就会进入动态方法解析流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <objc/runtime.h>
// 添加实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test1)) {
Method method = class_getInstanceMethod(self, @selector(instanceResolve));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 添加类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test2)) {
Method method = class_getInstanceMethod(self, @selector(instanceResolve));
// 注意这里第一个参数传的是元类对象
class_addMethod(object_getClass(self), sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveClassMethod:sel];
}

消息转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
// objc_msgSend([[NSObject alloc] init], aSelector)
return [[NSObject alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0] 方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// Do whatever you want
}

消息机制流程

super

super底层会转换为objc_msgSendSuper2函数的调用,接收2个参数

  • struct objc_super2
  • SEL
++
1
2
3
4
5
6
struct objc_super2 {
id receiver; // receiver是消息接收者
Class current_class; // current_class是receiver的Class对象
};
// 摘自 objc4-723 Apple 开源 OC 源代码

看一道招聘一个靠谱的iOS上的面试题

1
2
3
4
5
6
7
8
9
10
11
// 下面的代码打印什么?
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
1
2
// [self class] 根据消息机制可以转换成以下代码
objc_msgSend(self, sel_registerName("class"));

当调用[self class] 时,实际先调用的是 objc_msgSend函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找 - (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。

1
2
3
- (Class)class {
return object_getClass(self);
}
1
2
3
4
5
6
// [super class] 根据super底层实现可以转换成以下代码
objc_msgSendSuper2({
self,
class_getSuperclass(current_class)
},
sel_registerName("class"));

当调用[super class] 时,实际先调用的是 objc_msgSendSuper2函数,第一步先构造 objc_super2 结构体,第二步是去 Father这个类里去找 - (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(receiver, @selector(class))去调用,因为receiver = self,此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son。

在看一道面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
// 下面的代码输出什么?
@implementation Person : NSObject
- (id)init {
self = [super init];
if (self) {
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]);
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]);
NSLog(@"%d", [Person isKindOfClass:[Person class]]);
NSLog(@"%d", [Person isMemberOfClass:[Person class]]);
}
return self;
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}

打印结果为1,0,0,0

+isMemberOfClass:方法判断的方法调用者的类和方法参数中的类是否相等,NSObjec类调用object_getClass,得到的是NSObjec元类,Person也是同样道理,拿类和元类相比自然不想等,所以第二和第四个打印为false.

+(BOOL)isKindOfClass:方法判断的方法调用者的类的本身及其父类和方法参数中的类是否有相等的,可知NSObjec的原类的superclass指向NSObjec类,所以[NSObject isKindOfClass:[NSObject class]]为true。而[Person isKindOfClass:[Person class]]Person的元类是meta-Person,meta-Person本身和父类中都是元类,所以不会有相等的。

Runtime API

类 API

  • 动态创建一个类(参数:父类,类名,额外的内存空间)

    1
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
  • 注册一个类(要在类注册之前添加成员变量)

    1
    void objc_registerClassPair(Class cls)
  • 销毁一个类

    1
    void objc_disposeClassPair(Class cls)
  • 获取isa指向的Class

    1
    Class object_getClass(id obj)
  • 设置isa指向的Class

    1
    Class object_setClass(id obj, Class cls)
  • 判断一个OC对象是否为Class

    1
    BOOL object_isClass(id obj)
  • 判断一个Class是否为元类

    1
    BOOL class_isMetaClass(Class cls)
  • 获取父类

    1
    Class class_getSuperclass(Class cls)

成员变量 API

  • 获取一个实例变量信息

    1
    Ivar class_getInstanceVariable(Class cls, const char *name)
  • 拷贝实例变量列表(最后需要调用free释放)

    1
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
  • 设置和获取成员变量的值

    1
    2
    void object_setIvar(id obj, Ivar ivar, id value)
    id object_getIvar(id obj, Ivar ivar)
  • 动态添加成员变量(已经注册的类是不能动态添加成员变量的)

    1
    BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
  • 获取成员变量的相关信息

    1
    2
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)

属性 API

  • 获取一个属性

    1
    objc_property_t class_getProperty(Class cls, const char *name)
  • 拷贝属性列表(最后需要调用free释放)

    1
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
  • 动态添加属性

    1
    - BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount)
  • 动态替换属性

    1
    - void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount)
  • 获取属性的一些信息

    1
    2
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)

方法 API

  • 获得一个实例方法、类方法

    1
    2
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)
  • 方法实现相关操作

    1
    2
    3
    IMP class_getMethodImplementation(Class cls, SEL name)
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementations(Method m1, Method m2)
  • 拷贝方法列表(最后需要调用free释放)

    1
    Method *class_copyMethodList(Class cls, unsigned int *outCount)
  • 动态添加方法

    1
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
  • 动态替换方法

    1
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
  • 获取方法的相关信息(带有copy的需要调用free去释放)

    1
    2
    3
    4
    5
    6
    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char *method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)
  • 选择器相关

    1
    2
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)
  • 用block作为方法实现

    1
    2
    3
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)

Runtime 应用场景

  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现(交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题
  • ……

总结

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。

OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数,平时编写的OC代码,底层都是转换成了Runtime API进行调用。