关于Runtime你询问多少?

目录

  • 简介
  • 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的指针。

isa_t

再来看看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中:

如今版的Class结构体

cacge_t

cacge_t

cache_t,顾名思义,其实就是缓存,对应于老版本的cache。
_buckets 存储IMP_mask_occupied 对应 vtable

bucket_t

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_classdata方法,就是直接将 class_data_bits_t
data 方法重返,重临的是
class_rw_t品种,而以此值是bits与FAST_DATA_MASK按位与收获的结果。bits在内存中每个位的意义如下:

32位:

32位

64位包容版:

64位包容版

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,setBitsclearBits,对应到每个bool值的掩码都有函数封装,如:

    bool hasDefaultRR() {
        return getBit(FAST_HAS_DEFAULT_RR);
    }
    void setHasDefaultRR() {
        setBits(FAST_HAS_DEFAULT_RR);
    }
    void setHasCustomRR() {
        clearBits(FAST_HAS_DEFAULT_RR);
    }

现实的你可以看看源码,我就不详细贴出来了。

面前我们说了,那么些data归来的是bitsFAST_DATA_MASK位与得到的值,而那边FAST_DATA_MASK实际上就存储了指向class_rw_t的指针。

class_rw_t

class_rw_t

乍一看,那一个看似是存储类的点子、属性、协议等的,可是大家来看,那里还有一个class_ro_t,再持续看看

class_ro_t

class_ro_t

梳理一下,整个结构是如此的objc_class包含了class_data_bits_tclass_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
内具备重叠部分。于是在编译期会给instanceStartinstanceSize赋值,确定好编译时每个类的所占内存区域起头偏移量和分寸,这样只需将子类与基类的那五个变量作比较即可见道子类是还是不是与基类有重合,假诺有,也可分晓子类需求挪多少偏移量。

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

category_t
存储了体系中可以进行的实例方法、类方式、协议、实例属性和类属性。类特性是
Objective-C 2016 年新增的特点,沾 Swift 的光。所以
category_t中稍微成员变量是为着合营 Swift 的性状,Objective-C
暂没提供接口,仅做了底部数据结构上的格外。

再有为数不少数据结构,我就不一一贴出来了,源码中都是可以间接查看的。

二、音讯转发

当一个对象能吸纳一个音讯时,就会走正规的措施调用流程。但借使一个对象不能吸纳指定新闻时,就会启动所谓”音讯转载(message
forwarding)
“机制,通过这一机制,我们得以告知对象怎么着处理未知的新闻。默许情形下,对象吸收到未知的音信,会促成程序崩溃。

信息转载一共有三步:

  1. 动态方法分析
  2. 备用接收者
  3. 一体化转载

动态方法分析

目的在接到到未知的新闻时,首先会调用所属类的类方法+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”) 在内存中是那样存储的:

associateobjcect

好了,咱们回去对 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);
}
  1. 使用 old_association(0, nil) 成立一个暂时的 ObjcAssociation
    对象(用于所有原有的涉嫌对象,方便在章程调用的最后释放值)

new_value != nil的动静下

  1. 调用 acquireValuenew_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;
}
  1. 起首化一个
    AssociationsManager,并拿走唯一的保存关联对象的哈希表AssociationsHashMap

  2. 先使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap

  3. 只要没有找到,开首化一个 ObjectAssociationMap,再实例化
    ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects
    方法(它会将 isa 结构体中的标记位 has_assoc 标记为
    true),注脚当前目的涵盖关联对象

ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
  1. 假定找到了对应的 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);
}
  1. 末段,如果原本的涉嫌对象有值的话,会调用 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差不多

  1. 得到静态变量AssociationsHashMap

  2. DISGUISE(object)为 key 查找AssociationsHashMap

  3. void *keykey查找ObjcAssociation

  4. 根据 policy调用相应的法门

if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
      objc_retain(value);
 }

if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
     objc_autorelease(value);
 }
  1. 回到关联对象 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的明亮是必要的。

最后,文中有何错误的地方希望大家提出,希望和豪门共同进步。