目录
- 简介
- Runtime中的一些数据结构
- 消息转发
- 关联对象的实现原理
简介
因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个 Objc 运行框架的一块基石。
Runtime其实有两个版本: “modern” 和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行 (Modern) 版的 Runtime 系统,只能运行在 iOS 和 macOS 10.5 之后的 64 位程序中。而 maxOS 较老的32位程序仍采用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。
在OC中调用一个函数,其实就是向一个对象(类也是一个对象)发送一条消息,比如:
[receiver message]
会被编译器转化为
objc_msgSend(receiver, selector)
了解Runtime其实就是了解OC动态特性的底层实现,这对于我们理解OC这门语言非常有必要。
下面,你可以下载runtime的源码然后来跟我一起探索
一、Runtime中的一些数据结构
首先,在runtime源码的objc-private.h文件中我们可以看到对象和类都是一个结构体:
点进去我们一个一个查看
可以看到,对象主要就是一个包含isa变量的结构体,这个变量主要就是包含一个指向Class的指针。
再来看看Class结构体的具体定义。
在objc-runtime-old.h中,它主要包含这样一些数据结构:
struct objc_class : objc_object {
//继承自objc_object的isa指针
Class superclass; //指向父类的指针
const char *name; //类名
uint32_t version; //类的版本信息
uint32_t info; //类信息,提供一些运行期使用的一些标示位
uint32_t instance_size; //类的实例变量的大小
struct old_ivar_list *ivars; //类的成员变量链表
struct old_method_list **methodLists; //方法定义的链表
Cache cache; //方法缓存(用于消息发送时的查找)
struct old_protocol_list *protocols; //协议链表
}
可以看到,objc_class是继承自objc_object的,所以别忘了,objc_class也有一个isa指针。为什么类也有isa指针呢?我前面的文章曾经提到过,在创建类的时候,Runtime其实创建了元类(Meta Class),,所以类对象的所属类型就是元类,具体信息可以参考这篇文章。关于类的信息全都存在这个数据结构中,操作类其实就是操作这个结构体。
但是这是之前的runtime实现,现行版的Runtime源码在objc-runtime-new.h中:
cacge_t
cache_t,顾名思义,其实就是缓存,对应于老版本的cache。
_buckets
存储IMP
,_mask
和_occupied
对应 vtable
。
bucket_t
bucket_t 中就是存储了指针与 IMP 的键值对,以在方法查找的时候能够对缓存过的方法进行快速响应。
class_data_bits_t
objc_class中最负责的就是bits,对类的操作几乎就是围绕它展开
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
}
结合前面objc_class
的data
方法,就是直接将 class_data_bits_t
的data
方法返回,返回的是 class_rw_t
类型,而这个值是bits与FAST_DATA_MASK按位与得到的结果。bits在内存中每个位的含义如下:
32位:
64位兼容版:
64位不兼容版:
其中64位不兼容版每个宏对应如下:
// class is a Swift class
#define FAST_IS_SWIFT (1UL<<0)
// class's instances requires raw isa
#define FAST_REQUIRES_RAW_ISA (1UL<<1)
// class or superclass has .cxx_destruct implementation
// This bit is aligned with isa_t->hasCxxDtor to save an instruction.
#define FAST_HAS_CXX_DTOR (1UL<<2)
// data pointer
#define FAST_DATA_MASK 0x00007ffffffffff8UL
// class or superclass has .cxx_construct implementation
#define FAST_HAS_CXX_CTOR (1UL<<47)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_HAS_DEFAULT_AWZ (1UL<<48)
// class or superclass has default retain/release/autorelease/retainCount/
// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR (1UL<<49)
// summary bit for fast alloc path: !hasCxxCtor and
// !instancesRequireRawIsa and instanceSize fits into shiftedSize
#define FAST_ALLOC (1UL<<50)
// instance size in units of 16 bytes
// or 0 if the instance size is too big in this field
// This field must be LAST
#define FAST_SHIFTED_SIZE_SHIFT 51
可以看到,这里面除了FAST_DATA_MASK 是一段控件存储数据以外,其它都是用1bit来存储bool值保存信息class_data_bits_t
提供了三个方法用于位操作:getBit
,setBits
和clearBits
,对应到每个bool值的掩码都有函数封装,如:
bool hasDefaultRR() {
return getBit(FAST_HAS_DEFAULT_RR);
}
void setHasDefaultRR() {
setBits(FAST_HAS_DEFAULT_RR);
}
void setHasCustomRR() {
clearBits(FAST_HAS_DEFAULT_RR);
}
具体的你可以看看源码,我就不详细贴出来了。
前面我们说了,这个data
返回的是bits
与FAST_DATA_MASK
位与得到的值,而这里FAST_DATA_MASK
其实就存储了指向class_rw_t
的指针。
class_rw_t
乍一看,这个好像是存储类的方法、属性、协议等的,但是我们看到,这里还有一个class_ro_t
,再继续看看
class_ro_t
梳理一下,整个结构是这样的:objc_class
包含了class_data_bits_t
,class_data_bits_t
存储了class_rw_t
的指针,而class_rw_t
结构体又包含class_ro_t
指针。lass_ro_t
中的method_list_t
, Ivar_list_t
,property_list_t
结构体都继承自entsize_list_tt<Element, List, FlagMask>
。结构为xxx_list_t
的列表元素结构为xxx_t
,命名很工整。protocol_list_t
与前三个不同,它存储的是protocol_t *
指针列表,实现比较简单。
entsize_list_tt
实现了 non-fragile特性的数组结构。假如苹果在新版本的 SDK 中向 NSObject
类增加了一些内容,NSObject
的占据的内存区域会扩大,开发者以前编译出的二进制中的子类就会与新的 NSObject 内存有重叠部分。于是在编译期会给instanceStart
和instanceSize
赋值,确定好编译时每个类的所占内存区域起始偏移量和大小,这样只需将子类与基类的这两个变量作对比即可知道子类是否与基类有重叠,如果有,也可知道子类需要挪多少偏移量。
class_ro_t->flags
则存储了很多在编译时期就确定的类的信息,也是 ABI 的一部分。
总结:
class_rw_t
提供了运行时对类拓展的能力,而class_ro_t
存储的大多是类在编译时就已经确定的信息。二者都存有类的方法、属性(成员变量)、协议等信息,不过存储它们的列表实现方式不同。
class_rw_t
中使用的 method_array_t, property_array_t
, protocol_array_t
都继承自list_array_tt<Element, List>
, 它可以不断扩张,因为它可以存储 list 指针,内容有三种:
- 空
- 一个 entsize_list_tt 指针
- entsize_list_tt 指针数组
class_rw_t
的内容是可以在运行时被动态修改的,可以说运行时对类的拓展大都是存储在这里的。
class_rw_t->flags
存储的值并不是编辑器设置的,其中有些值可能将来会作为 ABI 的一部分。
demangledName
是计算机语言用于解决实体名称唯一性的一种方法,做法是向名称中添加一些类型信息,用于从编译器中向链接器传递更多语义信息。
Category
category_t
存储了类别中可以拓展的实例方法、类方法、协议、实例属性和类属性。类属性是 Objective-C 2016 年新增的特性,沾 Swift 的光。所以 category_t
中有些成员变量是为了兼容 Swift 的特性,Objective-C 暂没提供接口,仅做了底层数据结构上的兼容。
还有很多数据结构,我就不一一贴出来了,源码中都是可以直接查看的。
二、消息转发
当一个对象能接收一个消息时,就会走正常的方法调用流程。但如果一个对象无法接收指定消息时,就会启动所谓”消息转发(message forwarding)“机制,通过这一机制,我们可以告诉对象如何处理未知的消息。默认情况下,对象接收到未知的消息,会导致程序崩溃。
消息转发一共有三步:
- 动态方法解析
- 备用接收者
- 完整转发
动态方法解析
对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法””。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
备用接受者
如果在上一步无法处理消息,则Runtime会继续调以下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果一个对象实现了这个方法,并返回一个非nil且非self的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。
如:
- (id)forwardingTargetForSelector:(SEL)aSelector{
return [Test2 new];
}
此时发送的消息就会交给Test2的一个对象
注意:如果想替换类方法的接受者,需要覆写 + (id)forwardingTargetForSelector:(SEL)aSelector
方法,并返回类对象:
这里编译器没有代码补全提醒,且你在文档中是找不到这个方法的,但是经过实验确实是有这个方法的,且能顺利转发类方法。
完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
这里需要注意的是参数anInvocation
是从哪的来的呢?其实在forwardInvocation:
消息发送前,Runtime系统会向对象发送methodSignatureForSelector:
消息,并取到返回的方法签名用于生成NSInvocation
对象。所以我们在重写forwardInvocation:
的同时也要重写methodSignatureForSelector:
方法,否则会抛异常。
这一步转发和第二步转发的主要区别就是,它可以指定多个对象进行转发,且这些对象都需要实现相应的方法,否则依然会抛出异常。如:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString *sel = NSStringFromSelector(aSelector);
if ([sel isEqualToString:@"add"]) {
return [NSMethodSignature signatureWithObjCTypes:"v:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
if ([NSStringFromSelector(sel) isEqualToString:@"add"]) {
[anInvocation invokeWithTarget:[Test2 new]];
[anInvocation invokeWithTarget:[Test3 new]];
}
}
此时Test2和Test3两个类的对象都会转发这条消息。
三、关联对象的实现原理(Associated Objects)
这里我就不介绍关联对象的使用了,网上相关博客有很多,这里我们介绍关联对象是如果把一个对象关联起来的。
我们直接看关联对象相关的三个方法吧:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
objc_setAssociatedObject
我们直接看objc-runtime.mm中的源码
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
...
}
if (old_association.hasValue()) ReleaseValue()(old_association);
}
这里我省略了大多的实现代码,我们主要看它的实现原理就好。
主要注意这里的几个类和数据结构:
- AssociationsManager
- AssociationsHashMap
- ObjcAssociationMap
- ObjcAssociation
AssociationsManager
在源代码中的定义是这样的:
spinlock_t AssociationsManagerLock;
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
它维护了spinlock_t
和 AssociationsHashMap
的单例,初始化它的时候会调用 lock.lock() 方法,在析构时会调用lock.unlock()
,而 associations
方法用于取得一个全局的 AssociationsHashMap
单例。
也就是说 AssociationsManager
通过持有一个自旋锁 spinlock_t
保证对 AssociationsHashMap
的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作。
如何存储ObjcAssociation
AssociationsHashMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
AssociationsHashMap
用于保存从对象的 disguised_ptr_t
到 ObjectAssociationMap
的映射。
ObjectAssociationMap
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
ObjectAssociationMap
则保存了从key
到关联对象ObjcAssociation
的映射,这个数据结构保存了当前对象对应的所有关联对象
ObjcAssociation
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
ObjcAssociation
包含了 policy
以及 value
举一个简单的例子:
NSObject *obj = [NSObject new];
objc_setAssociatedObject(obj, @selector(hello), @"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
这里的关联对象 ObjcAssociation(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @"Hello") 在内存中是这么存储的:
好了,我们回到对 objc_setAssociatedObject
方法的分析
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
- 使用 old_association(0, nil) 创建一个临时的 ObjcAssociation 对象(用于持有原有的关联对象,方便在方法调用的最后释放值)
当new_value != nil
的情况下
- 调用
acquireValue
对new_value
进行retain
或者copy
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
初始化一个
AssociationsManager
,并获取唯一的保存关联对象的哈希表AssociationsHashMap
先使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法(它会将 isa 结构体中的标记位 has_assoc 标记为 true),表明当前对象含有关联对象
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
- 如果找到了对应的 ObjectAssociationMap,就要看 key 是否存在了,由此来决定是更新原有的关联对象,还是增加一个
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
- 最后,如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值
当new_value == nil
的情况下,其实就是调用 erase 方法,擦除 ObjectAssociationMap 中 key 对应的节点,删除对应key的关联对象。
objc_getAssociatedObject
前面objc_setAssociatedObject已经详细介绍了,下面这两个方法就很容易理解了。
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
它的逻辑和objc_setAssociatedObject
差不多
获取静态变量
AssociationsHashMap
以
DISGUISE(object)
为 key 查找AssociationsHashMap
以
void *key
为key
查找ObjcAssociation
根据
policy
调用相应的方法
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
- 返回关联对象 ObjcAssociation 的值
objc_removeAssociatedObjects
直接放代码吧
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
看到这里我想也没啥需要说的了,唯一需要注意一点的就是这里在删除之前,它加了个判断if (object && object->hasAssociatedObjects())
我们来看看这个hasAssociatedObjects
objc_object::hasAssociatedObjects()
{
if (isTaggedPointer()) return true;
if (isa.nonpointer) return isa.has_assoc;
return true;
}
如果是TaggedPointer,则返回true,正常对象则是根据isa的标记位来判断是否存在关联对象。
总结
Runtime是支持OC的一个非常强大的库,OC的很多特性都是依赖于Runtime,所以想要更好的掌握这门语言,对Runtime的理解是必要的。
最后,文中有什么错误的地方希望大家指出,希望和大家共同进步。