函数的调用在编写翻译的时候会控制调用哪个函数,Runtime其实有八个版本

目录

  • 简介
  • Runtime中的一些数据结构
  • 消息转载
  • 论及对象的达成原理

runtime
Objective-C是基于 C 的,它为 C
添加了面向对象的性状。它将洋洋静态语言在编写翻译和链接时代做的事放到了
runtime 运营时来拍卖

简介

因为Objc是一门动态语言,所以它连接想艺术把有个别控制工作从编写翻译连接推迟到运转时。也正是说只有编写翻译器是不够的,还亟需一个周转时系统
(runtime system) 来举行编写翻译后的代码。那就是 Objective-C Runtime
系统设有的意思,它是整整 Objc 运转框架的一块基石。

Runtime其实有五个版本: “modern” 和 “legacy”。大家前些天用的 Objective-C
2.0 选择的是现行反革命 (Modern) 版的 Runtime 系统,只好运维在 iOS 和 macOS
10.5 之后的 64 位程序中。而 maxOS 较老的叁十三个人程序仍接纳 Objective-C 1中的(早期)Legacy 版本的 Runtime
系统。这个本子最大的界别在于当你更改2个类的实例变量的布局时,在早先时期版本中您要求再一次编写翻译它的子类,而前几天版就不须求。

在OC中调用2个函数,其实便是向1个目的(类也是三个对象)发送一条音讯,比如:
[receiver message]
会被编写翻译器转化为
objc_msgSend(receiver, selector)
驾驭Runtime其实正是精通OC动态本性的底部达成,那对于大家掌握OC那门语言格外有须求。

上边,你能够下载runtime的源码下一场来跟本人一同研讨

对于 C 语言,函数的调用在编写翻译的时候会控制调用哪个函数。

一 、Runtime中的一些数据结构

先是,在runtime源码的objc-private.h文件中大家得以见到指标和类都是一个结构体:

指标与类的定义

点进去我们叁个二个查看

指标的结构体

能够看到,对象主要便是二个涵盖isa变量的结构体,这几个变量主要便是包蕴3个指向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结构体

OC的函数调用叫做音信发送,是动态调用的。在编写翻译的时候并不能够控制真正调用哪个函数,唯有在真正运维的时候才会依照函数的名目找到相应的函数达成来调用。

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位

六1肆位包容版:

63个人包容版

陆拾伍位不包容版:

六10位不包容版

里面六13人不包容版每一个宏对应如下:

// 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
是电脑语言用于解决实体名称唯一性的一种艺术,做法是向名称中添加一些类型音讯,用于从编写翻译器中向链接器传递更加多语义音讯。

从 NSObject 的初步化领会isa
Objective-C 对象都以 C 语言结构体,全部的对象都蕴含2个类型为 isa
的指针
当 ObjC
为为叁个对象分配内部存款和储蓄器,初阶化实例变量后,在那个指标的实例变量的结构体中的第②个就是isa。

Category

Category

category_t
存款和储蓄了品种中能够进行的实例方法、类方式、协议、实例属性和类属性。类性情是
Objective-C 二零一五 年新增的特点,沾 Swift 的光。所以
category_t中多少成员变量是为着合营 Swift 的性状,Objective-C
暂没提供接口,仅做了尾部数据结构上的匹配。

再有很多数据结构,笔者就不一一贴出来了,源码中都是能够直接查看的。

在class_data_bits_t 结构体中,只含有3个 64 位的 bits
用于存款和储蓄与类有关的音讯:
class_data_bits_t bits;
将 bits 与 FAST_DATA_MASK 进行位运算,只取个中的 [3, 47] 位转换来class_rw_t * 返回。
ObjC 类中的属性、方法还有依据的商谈等消息都保存在 class_rw_t 中:

② 、新闻转载

当1个对象能接过3个音讯时,就会走正规的法子调用流程。但假诺二个对象不可能吸收钦命新闻时,就会运维所谓”音讯转发(message
forwarding)
“机制,通过那第2建工公司制,大家得以告知对象怎样处理未知的音讯。默许景况下,对象吸收到未知的新闻,会造成程序崩溃。

新闻转载一共有三步:

  1. 动态方法分析
  2. 备用接收者
  3. 完整转载
struct class_rw_t {
    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;
};

动态方法分析

对象在收取到未知的新闻时,首先会调用所属类的类方法+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];
}

中间蕴蓄2个对准常量的指针
ro,在那之中蕴藏了现阶段类在编译期就曾经显著的性情、方法以及依照的协议。

备用接受者

只要在上一步不可能处理音讯,则Runtime会继续调以下方式:

- (id)forwardingTargetForSelector:(SEL)aSelector

若是三个指标达成了那个点子,并重回二个非nil且非self的结果,则那一个目的会作为信息的新接收者,且音信会被分发到那么些目的。假若大家一向不点名相应的对象来处理aSelector,则应该调用父类的落成来回到结果。

如:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [Test2 new];
}

此时发送的音讯就会提交Test2的3个目的

注意:如若想替换类方法的接受者,供给覆写
+ (id)forwardingTargetForSelector:(SEL)aSelector办法,并重临类对象:

那里编写翻译器没有代码补全提醒,且你在文书档案中是找不到那一个艺术的,然则通超过实际验确实是有其一格局的,且能顺利转载类方法。

在编写翻译时期类的布局中的 class_data_bits_t *data 指向的是1个class_ro_t * 指针:
下一场在加载 ObjC 运维时的时候调用 realizeClass 方法:

全部音讯转发

倘诺在上一步还不能够处理未知音信,则唯一能做的就是启用完整的新闻转发机制了。此时会调用以下形式:

- (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七个类的对象都会转接那条音讯。

  1. 从 class_data_bits_t 调用 data 方法,将结果从 class_rw_t
    强制转换为 class_ro_t 指针
  2. 起先化二个 class_rw_t 结构体
  3. 设置结构体 ro 的值以及 flag
  4. 说到底设置科学的 data。

③ 、关联对象的完毕原理(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
通过具有1个自旋锁
spinlock_t 保证对 AssociationsHashMap
的操作是线程安全的,即每便只会有2个线程对 AssociationsHashMap
实行操作

const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

怎么样存款和储蓄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的号子位来判断是还是不是留存涉嫌对象。

而是,在那段代码运维之后 class_rw_t
中的方法,属性以及协和式飞机列表均为空。那时需求 realizeClass 调用
methodizeClass
方法来将类本人完毕的法子(包含分类)、属性和遵守的协议加载到 methods、
properties 和 protocols 列表中。

总结

Runtime是协理OC的3个不行强劲的库,OC的好多表征都是注重于Runtime,所以想要更好的控制那门语言,对Runtime的知情是必需的。

终极,文中有怎么着错误的位置希望我们提议,希望和我们共同进步。

当实例方法被调用时,它要通过友好有着的 isa 来寻觅对应的类,然后在此地的
class_data_bits_t 结构体中搜寻对应措施的兑现。同时,每2个objc_class 也有多少个针对本身的父类的指针 super_class
用来寻找继承的不二法门。

音讯的调用进度

runtime进行曲,objc_msgSend的前生今生(二)

伪代码

// 首先看一下objc_msgSend的方法实现的伪代码
id objc_msgSend(id self, SEL op, ...) {
   if (!self) return nil;
   // 关键代码(a)
   IMP imp = class_getMethodImplementation(self->isa, SEL op);
   imp(self, op, ...); // 调用这个函数,伪代码...
}
// 查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) {
      ... // 执行动态绑定
    }
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; // 这个是用于消息转发的
    return imp;
}
// 遍历继承链,查找IMP
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }
    Class curClass = cls;
    IMP imp = nil;
    do { // 先查缓存,缓存没有时重建,仍旧没有则向父类查询
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass); // 关键代码(b)
    return imp;
}

objc_msgSend为何使用汇编语言

Objective-C
音讯发送与转向机制原理

其实在
objc-msg-x86_64.s
中带有了三个本子的 objc_msgSend
方法,它们是根据再次回到值的连串和调用者的品类分别处理的:

  • objc_msgSendSuper:向父类发新闻,重回值类型为 id
  • objc_msgSend_fpret:再次回到值类型为 floating-point,个中带有
    objc_msgSend_fp2ret 入口处理回来值类型为 long double 的情况
  • objc_msgSend_stret:重临值为结构体
  • objc_msgSendSuper_stret:向父类发音讯,重回值类型为结构体

当须求发送新闻时,编写翻译器会扭转中间代码,依据情形分别调用 objc_msgSend,
objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret
在那之中之一。
那也是干什么 objc_msgSend 要用汇编语言而不是 OC、C 或 C++
语言来落到实处,因为单独2个办法定义满意不断六系列型重返值,有的艺术重回id,有的重返int。考虑到差别门类参数再次回到值排列组合映射分歧方式签名(method
signature)的题材,那 switch 语句得老长了。。。那些原因能够总计为
Calling
Convention,约等于说函数调用者与被调用者必须预定好参数与重临值在分裂架构处理器上的存取规则,比如参数是以何种顺序存款和储蓄在栈上,或是存款和储蓄在什么寄存器上。除此之外还有其他原因,比如其可变参数用汇编处理起来最有益,因为找到
IMP 地址后参数都在栈上。假设用 C++ 传递可变参数那就喜剧了,prologue
机制会弄乱地址(比如 i386 上为了存款和储蓄 ebp 向后活动 4byte),最终还要用
epilogue 打扫战场。而且汇编制程序序执行效用高,在 Objective-C Runtime
中调用频率较高的函数好多都用汇编写的。

类方式的完成又是怎样寻找并且调用的呢?

亟需引入元类来保管不管类依然对象都能通过一样的建制查找方法的贯彻。
让每1个类的 isa
指向对应的元类,这样就高达了使类措施和实例方法的调用机制一样的指标:

  • 实例方法调用时,通过对象的 isa 在类中得到方式的落实
  • 类措施调用时,通过类的 isa 在元类中收获情势的落到实处

情势决议

从源代码看 ObjC
中国国投息的殡葬

选取子在此时此刻类和父类中都从不找到实现,就进去了艺术决议(method
resolve)的进程:

if (resolver  &&  !triedResolver) {
    _class_resolveMethod(cls, sel, inst);
    triedResolver = YES;
    goto retry;
}

那有的代码调用 _class_resolveMethod 来分析并未找到达成的艺术。

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        _class_resolveInstanceMethod(cls, sel, inst);
    }
    else {
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst,
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

新闻转载forwarding

在缓存、当前类、父类以及 resolveInstanceMethod:
都并未缓解完成查找的难点时,Objective-C
还为大家提供了最后2遍翻身的机遇,举办艺术转化:
Objective-C
音讯发送与转会机制原理

新闻转载路径的逻辑,回顾如下:
一 、先调用 forwardingTargetForSelector 方法得到新的 target 作为 receiver
重新履行 selector,假设回去的内容非法(为 nil 恐怕跟旧 receiver
一样),那就进去第3步。

二 、调用 methodSignatureForSelector
获取方式签名后,判断重回类型音讯是或不是正确,再调用 forwardInvocation 执行
NSInvocation 对象,并将结果回到。如若指标没完结methodSignatureForSelector 方法,进入第1步。

3、调用 doesNotRecognizeSelector 方法。

图片 1

消息发送与转载路径流程图.png

RunTime应用实例

自行模型解析

MJExtension
jsonmodel

根本代码
NSObject+MJProperty.m

............. 
        /**遍历这个类的父类*/
        [self enumerateClassesWithBlock:^(__unsafe_unretained Class c, BOOL *stop) {
            // 1.获得所有的成员变量
            unsigned int outCount = 0;
            /**
                class_copyIvarList 成员变量,提示有很多第三方框架会使用 Ivar,能够获得更多的信息
                但是:在 swift 中,由于语法结构的变化,使用 Ivar 非常不稳定,经常会崩溃!
                class_copyPropertyList 属性
                class_copyMethodList 方法
                class_copyProtocolList 协议
                */
            objc_property_t *properties = class_copyPropertyList(c, &outCount);

            // 2.遍历每一个成员变量
            for (unsigned int i = 0; i<outCount; i++) {
                MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
                property.srcClass = c;
                [property setKey:[self propertyKey:property.name] forClass:self];
                [property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
                [cachedProperties addObject:property];
            }

            // 3.释放内存
            free(properties);
        }];
............. 

JSONModel.m

.............        
        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

        //loop over the class properties
        for (unsigned int i = 0; i < propertyCount; i++) {

            JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

            //get property name
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            p.name = @(propertyName);

            //JMLog(@"property: %@", p.name);

            //get property attributes
            const char *attrs = property_getAttributes(property);
            NSString* propertyAttributes = @(attrs);
            NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];
.............

主意调换

Method
Swizzling

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

怎么在load方法中添加
您真正精通 load
方法么?

load 作为 Objective-C
中的3个艺术,与任何措施有十分大的分裂。它只是1个在全路文件被加载到运营时,在
main 函数调用在此之前被 ObjC 运营时调用的钩子方法。

+load 方法会在main 函数运维前调用,每种类、分类的load
方法都会被调用。被调用时,全部的 framework
都曾经加载到了运维时中(但某些类恐怕还未加载)。在2个类的 load
方法中调用其余类的方式,假若被调用的类还未load,并不会触发被调用的类的load
方法。

深远掌握Objective-C:Category
附加category到类的工作会先于+load方法的执行
+load的实践各种是先类,后category,而category的+load执行顺序是依照编写翻译顺序决定的。

为Category添加属性

深远驾驭Objective-C:Category
咱俩通晓在category里面是心有余而力不足为category添加实例变量的。不过大家不少时候需求在category中添加和目的关联的值,这么些时候能够求助关联对象来兑现。

MyClass+Category1.h:

#import "MyClass.h"

@interface MyClass (Category1)

@property(nonatomic,copy) NSString *name;

@end

MyClass+Category1.m:

#import "MyClass+Category1.h"
#import <objc/runtime.h>

@implementation MyClass (Category1)

+ (void)load
{
    NSLog(@"%@",@"load in Category1");
}

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,
                             "name",
                             name,
                             OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;
}

@end

关系对象 AssociatedObject
完全解析

涉嫌对象又是什么贯彻并且管理的呢:

  • 论及对象实际便是 ObjcAssociation 对象
  • 关联对象由 AssociationsManager 管理并在 AssociationsHashMap 存储
  • 目的的指针以及其对应 ObjectAssociationMap 以键值对的方式储存在
    AssociationsHashMap 中
  • ObjectAssociationMap 则是用以存款和储蓄关联对象的数据结构
  • 每贰个目的都有二个标志位 has_assoc 提示对象是或不是带有关联对象

使用音信转发完结热修复

JSPatch
实现原理详解

当调用一个 NSObject
对象不设有的方式时,并不会立时抛出万分,而是会通过多层转载,层层调用对象的
-resolveInstanceMethod:, -forwardingTargetForSelector:,
-methodSignatureForSelector:, -forwardInvocation: 等办法,在那之中最后-forwardInvocation: 是会有三个 NSInvocation 对象,这么些 NSInvocation
对象保存了这一个点子调用的持有音讯,包涵 Selector
名,参数和重返值类型,最重点的是有全体参数值,能够从那几个 NSInvocation
对象里得到调用的装有参数值。咱们能够想办法让每种须求被 JS
替换的主意调用最终都调到
-forwardInvocation:,就能够消除不能够得到参数值的标题了。

引用

Objective-C
音信发送与转化学工业机械制原理

从源代码看 ObjC
中国国投息的出殡

波及对象 AssociatedObject
完全解析

深远明白Objective-C:Category

runtime

Method
Swizzling

Objective-C的hook方案(一): Method
Swizzling

JSPatch
完结原理详解

相关文章