Runtime
Objective-C 语言是一门动态语言。它把一些决策从编译阶段、链接阶段推迟到运行时阶段
- 静态语言:在编译阶段就已确定所有变量的数据类型,同时也确定要调用的函数,以及函数的实现。常见的静态语言,如:C/C++、Java、C# 等。
- 动态语言:程序在运行时可以改变其结构。也就是说在运行时检查变量数据类型,同时在运行时才会根据函数名查找要调用的具体函数。如 Objective-C。
# isa
- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个联合体(union)结构,还使用位域来存储更多的信息
struct objc_object {
private:
isa_t isa; // 8 bytes
public:
...
}
union isa_t {
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ISA_BITFIELD的定义如下
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
联合体 isa_t 涉及到一个位域的概念.
因为部分数据在存储时候并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。
这里 ISA_BITFIELD 位域成员通过跟 bits 相与来取对应的值。
在 ISA_BITFIELD 中定义的参数的含义
# 类的结构
# objc_class
Objective-C 类是由 Class 类型来表示的
typedef struct objc_class *Class;可以看到Class是一个objc_class的指针
struct objc_class: objc_object {
// Class isa; 继承自objc_object
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // 用户获取具体类信息
class_rw_t *data() const {
return bits.data();
}
}
2
3
4
5
6
7
8
9
objc_class 继承自objc_object ,主要是继承了一个isa
# class_rw_t 结构体
通过 objc_class 的 bits & FAST_DATA_MASK 获取到 class_rw_t 结构体信息
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
2
3
struct class_rw_t {
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
// explicit_atomic 是为了安全操作
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
...
}
struct class_rw_ext_t { // class_rw_t 扩展
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容.
# class_ro_t 结构体
上述的 class_rw_ext_t 中 class_ro_t 结构体信息:
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;
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,存储了当前类在编译器就已经确定了都属性、方法及遵循的协议
# method_t 结构体
method_t 是对方法、函数的封装
using MethodListIMP = IMP;
struct method_t {
SEL name; // 方法、函数名
const char *types; // 编码 (返回值类型、参数类型)
MethodListIMP imp; // 指向方法、函数的指针(函数地址)
...
};
2
3
4
5
6
7
# imp属性
method_t 结构体中的 imp 代表函数的具体实现,底层源码中的定义如下:
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
2
3
4
5
# SEL 属性
SEL 代表方法、函数名,一般叫做选择器,底层结构跟 char * 类似
typedef struct objc_selector *SEL;
获取方法:
- 可以通过 @selector() 和 sel_registerName() 获得
- 可以通过 sel_getName() 和 NSStringFromSelector() 转成字符串
- 不同类中相同名字的方法,所对应的方法选择器是相同的
# types属性
这个 types 值表示什么呢?iOS 中提供了一个叫做 @encode 的指令,可以将具体的类型表示成字符串编码
Code | Meaning | Code | Meaning |
---|---|---|---|
c | A char | * | A character string (char *) |
i | An int | @ | An object (whether statically typed or typed id) |
s | A short | # | A class object (Class) |
l | A longl is treated as a 32-bit quantity on 64-bit programs. | : | A method selector (SEL) |
q | A long long | [array type] | An array |
C | An unsigned char | {name=type...} | A structure |
I | An unsigned int | (name=type...) | A union |
S | An unsigned short | bnum | A bit field of num bits |
L | An unsigned long | ^type | A pointer to type |
Q | An unsigned long long | ? | An unknown type (among other things, this code is used for function pointer) |
f | A float | d | A double |
B | A C++ bool or a C99 _Bool | v | A void |
# 方法缓存
Class 内部结构中有个方法缓存(cache_t),调用了方法之后会缓存在里面,他用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
这里的散列表的原理就是,通过 @selector(methodName) & _mask 获得一个索引值,通过这个索引就能很快在 buckets 中拿到对应的 bucket_t(key, _imp)
- 存放:如果生成的索引在 buckets 下已经存在 data 。那么他会把 index - 1,减到零了还没有空闲位置,它会从数组最大值开始继续往前找位置,直到有位置
- 获取:在拿到 bucket_t 后,会比较一下 key 与 @selector(methodName) 是否对应,如果不对应,那就回按照存放的那样方式一个一个找。如果存满了,buckets 就会走扩容
这就是空间换时间
# 其他
# id是什么,id和void *区别
- id: typedef struct objc_object *id;我们创建的一个对象或一个实例,其实就是一个objc_object结构体指针,而我们常用的id也是这个结构体指针。
- 区别
- void* 是c语言中的泛型指针,指代任意的指针类型。当返回值是一个地址或者指针的时候,返回值可以用void*表示,也可以用此类型来定义任意类型的指针变量表示“对带有无类型/未知内容的某些随机块内存的引用”,只是一个指针,无法编辑指向地址上的内容。
- id是OC语音中的泛型指针,指代任意对象类型的指针。当返回值是一个对象指针的时候,返回值类型都可以用id表示,也可以用此类型来定义任意类型的对象指针变量。表示“对未知类的一些随机Objective-C对象的引用”
# 什么是元类?为什么要这么设计?
元类,和类对象一样,也是一个对象。它存储了一个类的所有类方法。
元类使用基类的元类作为他们的类,而所有类的基类都是NSObject(大多数),所以大多数元类使用NSObject的元类作为它的类。
基类的元类就是它自己的类(isa指向自己)
# 消息机制
首先编译器将方法调用编译为objc_msgSend(obj, @selector(methodName));
- obj 消息接受者
- @selector(methodName) 消息名称
objc_msgSend 的执行流程可以分为 3 大阶段
- 消息发送
- 找不到消息发送方法,就会进入动态方法解析,允许开发者动态创建新方法
- 如果动态方法解析没有做任何操作,这时候开始进入消息转发
如果三个阶段都没找到合适的方法调用,就会报错: unrecognized selector sent to instance
# 消息发送
- 通过obj的isa指针找到obj对应的class对象
- 在class对象的cache中通过SEL查找对应的函数method
- 若cache中找到,直接通过method中的函数指针跳转到对应函数执行。若找不到,去class_rw_t中的methods找
- 若methods中找到,则将method加入到cache中,方便下次查找,并通过method中的函数指针跳转到对应函数执行。若找不到,则去superClass中查找
- 若superClass中找到,则将method加入到cache中,方便下次查找,并通过method中的函数指针跳转到对应函数执行。若找不到,依次通过superClass查找,最后都找不到则进入动态解析流程。
# 动态方法解析
- 动态方法解析:向当前类发送resolveInstanceMethod:或resolveClassMehtod: 消息,检查是否动态向该类添加了方法。
- 动态解析后,会重新走“消息发送”的流程,从receiverClass的cache中查找方法这一步开始执行
# 消息转发
- 快速消息转发:检查该类是否实现了forwardingTargetForSelector:方法,若实现了,则调这个方法。若该方法返回非nil且非self,则向该返回对象重新进行发送消息
- 标准消息转发:runtime发送methodSignatureForSelector:消息获得selector对应的方法签名。若返回值非空,则通过forwardInvocation:转发消息。若返回为空,则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。
# Runtime应用
# 字典转模型
- 利用 Runtime 遍历所有的属性或者成员变量
- 利用 KVC 设值
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super init];
if (self) {
unsigned int count = 0;
objc_property_t *propertys = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = propertys[i];
//通过 property_getName 函数获得属性的名称
const char *name = property_getName(property);
NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
id value = [dict objectForKey:nameStr];
[self setValue:value forKey:nameStr];
}
free(propertys);
}
return self;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 替换方法实现
替换方法实现常用的两个方法:
// 方法替换
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
// 方法交换
void method_exchangeImplementations(Method m1, Method m2)
2
3
4
# 对象自动归档
用 Runtime 提供的函数遍历 Model 自身所有属性,并对属性进行 encode 和 decode 操作
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 分类添加属性
category里面无法添加实例变量,但是我们很多时候需要在category中添加和对象关联的值,这个时候就需要用到关联对象来实现了。
- (void)setAddName:(NSString *)addName {
objc_setAssociatedObject(self, @selector(addName), addName, OBJC_ASSOCIATION_COPY);
}
- (NSString *)addName {
return objc_getAssociatedObject(self, @selector(addName));
}
2
3
4
5
6
7