旧文新发,良久没有发文了。做许多需求或许是技能细节验证的时分会用到 Runtime 技能,用了挺久的了,本文就写一些场景和源码剖析相关的文章。 先问几个小问题:

  1. class_rw_t的结构是数组,数组里边的元素是数组,那它是二维数组吗?
  2. 为什么16字节对齐的?
  3. 有类目标、为什么规划元类目标?
  4. Super 原理的什么?

阅读完本文,你会掌握 Runtime 的原理和细节

动态言语

Runtime 是完成 OC 言语动态的 API。

静态言语:在编译阶段确认了变量数据类型、函数地址等,无法动态修正。

动态言语:只要在运转的时分才能够决议变量归于什么类型、办法真正的地址,

目标 objc_object 存了:isa、成员变量的值

类 objc_class: superclass、成员变量、实例变量

@interface Person : NSObject
{
    NSString *_name; 
}
@property (nonatomic, strong) NSString *hobby;
@end
malloc_size((__bridge const void *)(p))    // 24 isa占8字节 + _name 指针占8字节 + hobby 指针占8字节 = 24 
class_getInstanceSize(p.class)             // 32 ,体系内存对齐 

为什么体系是由16字节对齐的?

成员变量占用8字节对齐,每个目标的第一个都是 isa 指针,必需求占用8字节。举例一个极端 case,假设 n 个目标,其间 m 个目标没有成员变量,只要 isa 指针占用8字节,其间的 n-m个目标既有 isa 指针,又有成员变量。每个类交错摆放,那么 CPU 在拜访目标的时分会耗费许多时刻去识别详细的目标。许多时分会取舍,这个 case 便是时刻换空间。以16字节对齐,会加快拜访速度(参阅链表和数组的规划)

class_rw_t、class_ro_t、class_rw_ext_t 差异?

class_ro_t 在编译时期生成的,class_rw_t 是在运转时期生成的。

那么什么是 class_rw_ext_t?首要清晰2个概念

  • clean memory:加载后不会被修正。当体系内存严重时,能够从内存中移除,需求时能够再次加载

  • dirty memory:加载后会被修正,一向处于内存中

Runtime 初始化的时分,遇到一个类,则会运用类的 class_ro_t 中的根底信息(methods、properties、protocols)来创建 class_rw_t 目标。class_rw_t 规划的意图便是为了 Runtime 所需(Category 增加特点、协议、动态增加办法等),但是实践上那么多类大多数状况只要少部分类才需求 Runtime 才能。所以 Apple 为了内存优化,在 iOS 14 对 class_rw_t 拆分出 class_rw_ext_t,用来存储 Methods、Protocols、Properties 信息,会在运用的时分才创建,节省更多内存。

比方拜访 method 的进程

// 新版
const method_array_t methods() const {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
    } else {
        return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
    }
}

有类目标、为什么规划元类目标

复用音讯机制。比方 [Person new]

元类目标: isa、元类办法、

objc_msgSend 规划初衷便是为了音讯发送很快。假如没有元类,则类办法也存储在类目标的办法信息中,则或许需求加额外的字段来标记某个办法是类办法仍是目标办法。遍历或许寻觅会比较慢。所以引入元类(单一职责),规划元类的意图便是为了进步 objc_msgSend 的效率。

isa 实质

在 arm64 架构之前,isa 便是一个一般的指针,存储着 Class或Meta-Class 目标的内存地址。

在 arm64 之后,对 isa 进行了优化,变成了一个共用体(union)结构,还运用位域来存储更多的信息。

union isa_t
{
    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)
    };
};

struct 内部的成员变量能够指定占用内存位数, uintptr_t nonpointer : 1 代表占用1个字节

其间,结构体里边的归于”位域“

  • 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(刚创建出的目标,检查这个信息位0,由于存储着-1之后的引证计数)

  • has_sidetable_rc:引证计数器是否过大无法存储在isa中;假如为1,那么引证计数会存储在一个叫SideTable的类的特点中

上面说的更快,是怎么得出定论的?

检查 objc4 源代码看到目标履行销毁函数的时分会判别目标是否有相关目标、析构函数,有的话别离调用析构函数、移除相关目标等逻辑。

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}

isa 在 arm64 之后有必要经过 ISA_MASK 去查询 class(类目标、元类目标) 真正的地址

0x0000000ffffffff8ULL 用程序员形式打开计算器

深度剖析 Runtime

其间,结构体中的数据寄存大体是下面的结构:

extra_rc、has_sidetable_rc、deallocating、weakly_referenced、magic、shiftcls、has_has_cxx_dtor、assoc、nonpointer

知道结构体能够指定存储大小这个功用后,能够看到 isa_t 联合体与 ISA_MASK 按位与之后的地址,其实便是类实在的地址信息(或许是类目标、也有或许是元类目标)

假如要找出下面中心的 1010 怎么完成?按位与即可,且要找的方位补充位1,其他方位为0

0b0010 1000
0b0011 1100
-----------
0b0010 1000

定论:依据按位与的作用。ISA_MASK 的后3位都是0,所以咱们找到的类地址二进制表明时后3位一定为0

咱们能够验证下

Person *p = [[Person alloc] init];
NSLog(@"%p", [p class]);    // 0x1000081d8
NSLog(@"%p", object_getClass([Person class])); // 0x100008200
NSLog(@"%p", object_getClass([NSObject class])); // 0x7ff84cb29fe0
NSLog(@"%p", object_getClass([NSString class])); // 0x7ff84c9dcc28

为什么有的结束是8?

16进制的8转为二进制,0x1000

关于这部分的调试,需求在真机上运转,真机上 arm64,复制目标地址到体系自带的运算器(程序员形式),检查64位地址。依照下面的顺序一一检查

extra_rc、has_sidetable_rc、deallocating、weakly_referenced、magic、shiftcls、has_has_cxx_dtor、assoc、nonpointer

所以能够依据 isa 信息检查目标是否创建过相关目标、有没有设置弱引证、

模仿体系位运算规划 API

体系许多 API 都有位或运算。比方 KVO 中的 options,能够传递 NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld ,那么体系是怎么知道我到底传递了哪几个值?

按位或运算

0b0000 0001 // 1
0b0000 0010 // 2 
0b0000 0100 // 4
------------
0b0000 0111 // 7

能够看到上面3个数,按位或之后的成果为 0b0000 0111

按位与运算。

0b0000 0111
0b0000 0001
-----------
0b0000 0001
0b0000 0111
0b0000 0010
-----------
0b0000 0010
0b0000 0111
0b0000 0100
-----------
0b0000 0100
0b0000 0111
0b0000 1000
-----------
0b0000 0000

咱们发现上面3个数按位或之后的数字,别离与每个数按位与,得到的成果便是数据本身。

与一个不是3个数之一的数按位与,得到的成果为0b0000 0000。运用这个特性咱们能够判别传递来的参数是不是包含了某个值

typedef enum {
    OptionsEast = 1<<0,    // 0b0001
    OptionsSouth = 1<<1,   // 0b0010
    OptionsWest = 1<<2,    // 0b0100
    OptionsNorth = 1<<3    // 0b1000
} Options;
- (void)setOptions:(Options)options
{
    if (options & OptionsEast) {
        NSLog(@"我自东边来");
    }
    if (options & OptionsSouth) {
        NSLog(@"我自南边来");
    }
    if (options & OptionsWest) {
        NSLog(@"我自西边来");
    }
    if (options & OptionsNorth) {
        NSLog(@"我自北边来");
    }
}
[self setOptions: OptionsWest | OptionsNorth];
// 我自西边来
// 我自北边来

类目标 Class 的结构

检查 objc4 源代码看看

struct objc_object {
private:
    isa_t isa;
}
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
};

结构体承继于 objc_object 等同于下面代码

struct objc_class : objc_object {
    isa_t isa;
    Class superclass;
    cache_t cache;             // 办法缓存
    class_data_bits_t bits;    // 用于获取详细的类信息
};
struct class_rw_t {
    // 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;
};
struct class_data_bits_t {
    // Values are the FAST_ flags above.
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
}

能够看到 objc_class 获取 bits 里的实在数据需求经过按位与 FAST_DATA_MASK

struct class_ro_t {
    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;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

详细联系整理如下图

深度剖析 Runtime

阐明:

  • class_rw_t里边的 methods、properties、protocols 是数组(数组元素是也是办法组成的 Array),是可读可写的,包含了类的初始内容、分类的内容。

    为什么不是二维数组?由于Array 中的子 Array长度不一致,且不能补空

深度剖析 Runtime

```c
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
    isMeta = cls->isMetaClass();
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    bool isMeta = cls->isMetaClass();
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));
    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    auto rw = cls->data();
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
```
检查 objc4 源码发现针对类本身信息、Category 信息会进行组合。
  • class_ro_t 里边的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的(原始信息)初始内容

深度剖析 Runtime

Method_t

method_t 是对办法\函数的封装

struct method_t {
    SEL name; // 函数名、办法名    
    const char *types;    // 编码(回来值类型、参数类型)
    IMP imp;    // 指向函数的指针(函数地址)
}

IMP 代表函数的详细完成

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);

SEL 代表办法、函数名,一般叫做选择器,底层结构跟 char * 相似

typedef struct objc_selector *SEL;
  • 能够经过 @selector()sel_registerName() 获得

  • 能够经过 sel_getName()NSStringFromSelector() 转成字符串

  • 不同类中相同姓名的办法,所对应的办法选择器是相同的

types 包含了函数回来值、参数编码的字符串。回来值|参数1|参数2| ... | 参数n

Type Encoding

iOS 中供给了一个叫做 @encode 的指令,能够将详细的类型表明成字符串编码

深度剖析 Runtime

- (int)calcuate:(int)age heigith:(float)height;

比方这个办法的 type encoding 为 i24@0:8i16f20

解读下,上面的办法其实携带了2个根底参数。

(id)self _cmd:(SEL)_cmd

i 代表办法回来值为 int

24 代表参数共占24个字节大小。4个参数别离为 id 类型的 selfSEL 类型的 _cmd, int 类型的 age、float 类型的 height。8+8+4+4 共24个字节(id、SEL 都为指针,长度为8)

@ 代表第一个参数为 object 类型,从第0个字节开端

:代表第二个参数为 SEL,从第8个字节开端

i 代表第三个参数为 int,从第16个字节开端

f 代表第四个参数为 float,从第20个字节开端

办法缓存

调用办法的实质,比方说目标办法,先依据目标的 isa 找到类目标,在类目标的 method_list_t 类型的 methods 办法数组(Array 中的元素是办法 Array)中(类的Category1、类的 Category2…类本身的办法)查找办法,找不到则调用 superclass 查找父类的 methods 办法数组(Array 中的元素是办法 Array),效率较低,所以为了便利,给类设置了办法缓存。比方调用 Student 目标的 eat 办法,eat 在 student 中不存在,经过 isa 不断找,在 Person 类中找到了,则将 Person 类中的 eat 办法缓存在 Student 的 cache_t 类型的 cache 中。

Class 内部结构中有个办法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的办法,能够进步办法的查找速度

所以完好结构为:先依据目标的 isa 找到类目标,在类目标的 cache 列表中查找办法完成,假如找不到,则去 method_list_t 类型的 methods 办法数组(Array 中的元素是办法 Array)中(类的Category1、类的 Category2…类本身的办法)查找办法,找不到则调用 superclass 查找父类的 cache 中查找,找到则调用办法,一起将父类 cache 缓存中的办法,在子类的 cache 中缓存一边。父类 cache 没找到,则在 methods 办法数组(Array 中的元素是办法 Array)查找,找到则调用,一起在子类 cache 中缓存一份。父类 methods 办法数组(Array 中的元素是办法 Array)没找到则持续调用 superclass,顺次类推

struct cache_t {
    struct bucket_t *_buckets; // 散列表
    mask_t _mask;              // 散列表的参数 -1
    mask_t _occupied;          // 已经缓存的办法数量
}
struct bucket_t {
private:
    cache_key_t _key;    // SEL 作为 key
    IMP _imp;            // 函数的内存地址
}

_buckets -> | bucket_t |bucket_t |bucket_t |bucket_t |…

办法缓存查找原理,散列表查找

objc4 源码 objc-cache.mm

bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);
    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);
    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

散列表不行了,则会哈希拓容,此刻缓存会开释 cache_collect_free

void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    uint32_t oldCapacity = capacity();
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }
    reallocate(oldCapacity, newCapacity);
}
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
    bool freeOld = canBeFreed();
    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);
    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this
    assert(newCapacity > 0);
    assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
    setBucketsAndMask(newBuckets, newCapacity - 1);
    if (freeOld) {
        cache_collect_free(oldBuckets, oldCapacity);
        cache_collect(false);
    }
}

哈希查找元素中心是一个求 key 的进程,Java 中是求余,iOS 中是按位与 key & mask

static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
    return (mask_t)(key & mask);
}

空间换时刻的一个完成。

查找类的办法缓存 Demo

#import <Foundation/Foundation.h>
#ifndef MockClassInfo_h
#define MockClassInfo_h
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};
struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};
struct eint main () {
    GoodStudent *goodStudent = [[GoodStudent alloc] init];
    mock_objc_class *goodStudentClass = (__bridge mj_objc_class *)[GoodStudent class];
    [goodStudent goodStudentTest];
    [goodStudent studentTest];
    [goodStudent personTest];
    return 0;
}ntsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};
struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};
struct method_list_t : entsize_list_tt {
    method_t first;
};
struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};
struct property_t {
    const char *name;
    const char *attributes;
};
struct property_list_t : entsize_list_tt {
    property_t first;
};
struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};
struct class_ro_t {
    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;
};
struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 办法列表
    property_list_t *properties;    // 特点列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};
#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};
/* OC目标 */
struct mock_objc_object {
    void *isa;
};
/* 类目标 */
struct mock_objc_class : mock_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    mock_objc_class* metaClass() {
        return (mock_objc_class *)((long long)isa & ISA_MASK);
    }
};
#endif /* MockClassInfo_h */
@interface Person : NSObject
- (void)personSay;
@end
@interface Student : Person
- (void)studentSay;
@end
@interface GoodStudent : Student
- (void)goodStudentSay;
@end
int main () {
    GoodStudent *goodStudent = [[GoodStudent alloc] init];
    mock_objc_class *goodStudentClass = (__bridge mj_objc_class *)[GoodStudent class];
    // breakpoints1
    [goodStudent goodStudentSay];
    // breakpoints2
    [goodStudent studentSay];
    // breakpoints3
    [goodStudent personSay];
    // breakpoints4    
    [goodStudent goodStudentSay];
    // breakpoints5
    [goodStudent studentSay];
    // breakpoints6
    NSLog(@"well donw");
    return 0;
}

流程:

断点1的当地能够看到 mock_objc_class 结构体 cache_occupied 为1,_mask 为3,初始化哈希表长度为4

在断点1的当地,_occupied 为1则代表只要 init 办法被缓存,本行代码履行完,_occupied 为2.

在断点2的当地,_occupied 为2则代表只要 init、goodStudentSay 办法被缓存。本行代码履行完,_occupied 为3

在断点3的当地,_occupied 为3则代表只要 init 、goodStudentSay 、studentSay办法被缓存。本行代码履行完,_occupied 为1,且 _mask 为7。

奇了怪了,为什么 _occupied为1,且_mask 为7?

由于哈希表长度为4,缓存3个办法后,到第4个办法需求缓存的时分会履行哈希表拓容,缓存会失效。拓容战略为乘以2 即 uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE; 所以长度为8,mask 为长度-1 ,则为7,第4个办法刚好被缓存下来,_occupied 为1。

void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    uint32_t oldCapacity = capacity();
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }
    reallocate(oldCapacity, newCapacity);
}

持续运转

在断点4的当地,_occupied 为1则代表只要 personSay办法被缓存。本行代码履行完,_occupied 为2,且 _mask 为7。

在断点5的当地,_occupied 为2则代表只要 personSay、goodStudentSay 办法被缓存。本行代码履行完,_occupied 为3,且 _mask 为7。

在断点6的当地,_occupied 为3则代表只要 personSay、goodStudentSay、studentSay 办法被缓存, _mask 为7。

怎么依据办法散列表查找某个办法

GoodStudent *student = [[GoodStudent alloc] init];
mock_objc_class *studentClass = (__bridge mock_objc_class *)[GoodStudent class];
[student goodStudentSay];
[student studentSay];
[student personSay];
NSLog(@"Well done");
cache_t cache = studentClass->cache;
bucket_t *buckets = cache._buckets;
bucket_t bucket = buckets[(long long)@selector(personSay) & cache._mask];
NSLog(@"%s %p", bucket._key, bucket._imp);
// personSay 0xbec8

深度剖析 Runtime

原理便是依据类目标结构体找到 cache 结构体,cache 结构体内部的 _buckets 是一个办法散列表,检查源代码,依据散列表的哈希寻觅战略 (key & mask) 找到哈希索引,然后找到办法目标 bucket,其间寻觅办法索引的 key 便是 办法 selector。

static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
    return (mask_t)(key & mask);
}

objc_msgSend

oc 办法(目标办法、类办法)调用实质便是 objc_msgSend

[person eat];
objc_msgSend(person, sel_registerName("eat")); 
[Person initialize];
objc_msgSend([Person class], sel_registerName("initialize")); 

objc_msgSend 能够分为3个阶段:

  • 音讯发送

  • 动态办法解析

  • 音讯转发

检查源码 objc-msg-arm64.s

ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START
// x0 寄存器代表音讯接受者,receiver。objc_msgSend(person, sel_registerName("eat")) 的 person
    cmp    x0, #0            // nil check and tagged pointer check
    // b 代表指令跳转。le 代表 小于等于。<=0则跳转到 LNilOrTagged
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
    ldr    x13, [x0]        // x13 = isa // ldr 代表加载指令。这儿的意思是将 x0 寄存器信息写入到 x13中
    and    x16, x13, #ISA_MASK    // x16 = class    // 这儿便是将 x13 与  ISA_MASK 按位与,然后得到实在的 isa 信息,然后写入到 x16 中
LGetIsaDone:
    CacheLookup NORMAL        // calls imp or objc_msgSend_uncached // 这儿履行 objc_msgSend_uncached 逻辑,CacheLookup 是一个汇编宏,看下面的阐明
LNilOrTagged:
    // 判别为 nil 则跳转到  LReturnZero
    b.eq    LReturnZero        // nil check
    // tagged
    mov    x10, #0xf000000000000000
    cmp    x0, x10
    b.hs    LExtTag
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add    x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr    x16, [x10, x11, LSL #3]
    b    LGetIsaDone
LExtTag:
    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add    x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr    x16, [x10, x11, LSL #3]
    b    LGetIsaDone
LReturnZero:
    // x0 is already zero
    mov    x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    MESSENGER_END_NIL
    // 汇编中 ret 代表 return
    ret 
    END_ENTRY _objc_msgSend
.macro CacheLookup // 汇编宏,能够看到依据 (SEL & mask) 来寻觅真正的办法地址
    // x1 = SEL, x16 = isa
    ldp    x10, x11, [x16, #CACHE]    // x10 = buckets, x11 = occupied|mask
    and    w12, w1, w11        // x12 = _cmd & mask
    add    x12, x10, x12, LSL #4    // x12 = buckets + ((_cmd & mask)<<4)
    ldp    x9, x17, [x12]        // {x9, x17} = *bucket
1:    cmp    x9, x1            // if (bucket->sel != _cmd)
    b.ne    2f            //     scan more
    CacheHit $0            // call or return imp
2:    // not hit: x12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp    x12, x10        // wrap if bucket == buckets
    b.eq    3f
    ldp    x9, x17, [x12, #-16]!    // {x9, x17} = *--bucket
    b    1b            // loop
3:    // wrap: x12 = first bucket, w11 = mask
    add    x12, x12, w11, UXTW #4    // x12 = buckets+(mask<<4)
    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.
    ldp    x9, x17, [x12]        // {x9, x17} = *bucket
1:    cmp    x9, x1            // if (bucket->sel != _cmd)
    b.ne    2f            //     scan more
    CacheHit $0            // call or return imp
2:    // not hit: x12 = not-hit bucket
    // 这儿是办法查找失利,则走 checkMiss 逻辑,详细看下面
    CheckMiss $0            // miss if bucket->sel == 0
    cmp    x12, x10        // wrap if bucket == buckets
    b.eq    3f
    ldp    x9, x17, [x12, #-16]!    // {x9, x17} = *--bucket
    b    1b            // loop
3:    // double wrap
    JumpMiss $0
.endmacro
// CheckMiss 汇编宏,上面走 Normal 逻辑,内部走 __objc_msgSend_uncached 流程
.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz    x9, LGetImpMiss
.elseif $0 == NORMAL
    cbz    x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz    x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
// __objc_msgSend_uncached 内部其实走  MethodTableLookup 逻辑
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup
br    x17
END_ENTRY __objc_msgSend_uncached
// MethodTableLookup 是一个汇编宏,内部指令跳转到 __class_lookupMethodAndLoadCache3。
.macro MethodTableLookup
    // push frame
    stp    fp, lr, [sp, #-16]!
    mov    fp, sp
    // save parameter registers: x0..x8, q0..q7
    sub    sp, sp, #(10*8 + 8*16)
    stp    q0, q1, [sp, #(0*16)]
    stp    q2, q3, [sp, #(2*16)]
    stp    q4, q5, [sp, #(4*16)]
    stp    q6, q7, [sp, #(6*16)]
    stp    x0, x1, [sp, #(8*16+0*8)]
    stp    x2, x3, [sp, #(8*16+2*8)]
    stp    x4, x5, [sp, #(8*16+4*8)]
    stp    x6, x7, [sp, #(8*16+6*8)]
    str    x8,     [sp, #(8*16+8*8)]
    // receiver and selector already in x0 and x1
    mov    x2, x16
    bl    __class_lookupMethodAndLoadCache3
    // imp in x0
    mov    x17, x0
    // restore registers and return
    ldp    q0, q1, [sp, #(0*16)]
    ldp    q2, q3, [sp, #(2*16)]
    ldp    q4, q5, [sp, #(4*16)]
    ldp    q6, q7, [sp, #(6*16)]
    ldp    x0, x1, [sp, #(8*16+0*8)]
    ldp    x2, x3, [sp, #(8*16+2*8)]
    ldp    x4, x5, [sp, #(8*16+4*8)]
    ldp    x6, x7, [sp, #(8*16+6*8)]
    ldr    x8,     [sp, #(8*16+8*8)]
    mov    sp, fp
    ldp    fp, lr, [sp], #16
.endmacro

Tips:c 办法在汇编中运用的时分,需求在办法名前加 _ 。所以在汇编中某个办法为 _xxx,则在其他当地查找完成,需求去掉 _ 此刻 __class_lookupMethodAndLoadCache3 在汇编中没有完成,则依照 _class_lookupMethodAndLoadCache3 查找

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();
    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.
    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.
    runtimeLock.read();
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();
        realizeClass(cls);
        runtimeLock.unlockWrite();
        runtimeLock.read();
    }
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
 retry:    
    runtimeLock.assertReading();
    // Try this class's cache.
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }
    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }
    // No implementation found. Try method resolver once.
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }
    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
 done:
    runtimeLock.unlockRead();
    return imp;
}

音讯发送阶段

上面的代码走到 getMethodNoSuper_nolock 寻觅类里的办法

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();
    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?
    // 这儿依据类结构体找到 data(),然后找到 methods (Array 数组,数组元素是办法 Array)
    /*
    data() 其实便是 class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    */
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }
    return nil;
}
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    // 排好序则调用 `findMethodInSortedMethodList`,其内部是二分查找完成。
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // 没排序则线性查找 
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }
#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif
    return nil;
}
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);
    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        uintptr_t probeValue = (uintptr_t)probe->name;
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

cls->data()->methods.beginLists 这儿依据类结构体调用到 data() 办法,获取到 class_rw_t

class_rw_t *data() {
    return bits.data();
}

然后经过 class_rw_t 找到 methods (Array 数组,数组元素是办法 Array)。内部调用 search_method_list 办法。

search_method_list 办法内部判别办法数组是否排好序

  • 排好序则调用 findMethodInSortedMethodList,其内部是二分查找完成。

  • 没排序,则线性查找 (Linear search of unsorted method list)

getMethodNoSuper_nolock 履行完则会将办法写入到当时类目标的缓存中。

static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
#else
    _collecting_in_critical();
    return;
#endif
}
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();
    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;
    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return;
    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel);
    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();
    }
    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    bucket->set(key, imp);
}

摘出 lookUpImpOrForward 办法中的一段代码

// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
    Method meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, meth->imp, sel, inst, cls);
        imp = meth->imp;
        goto done;
    }
}
// Try superclass caches and method lists.

假如代码没有找到,则不会 gotodone,开端走父类缓存查找逻辑

// Try superclass caches and method lists.
{
    unsigned attempts = unreasonableClassCount();
    // for 循环不断查找,找当时类的父类,直到当时类为 nil。
    for (Class curClass = cls->superclass;
            curClass != nil;
            curClass = curClass->superclass)
    {
        // Halt if there is a cycle in the superclass chain.
        if (--attempts == 0) {
            _objc_fatal("Memory corruption in class list.");
        }
        // Superclass cache.
        // 先在父类的办法缓存中查找(依据 sel & mask)`cache_getImp` ,找到则将办法写入到本身类的办法缓存中去 `log_and_fill_cache(cls, imp, sel, inst, curClass);`
        imp = cache_getImp(curClass, sel);
        if (imp) {
            if (imp != (IMP)_objc_msgForward_impcache) {
                // Found the method in a superclass. Cache it in this class.
                log_and_fill_cache(cls, imp, sel, inst, curClass);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }
        // Superclass method list.
        // 假如在父类的办法缓存中没找到,则调用 `getMethodNoSuper_nolock` 父类的 办法数组(Array 元素为办法数组),依照排序好和没排序好别离走二分查找和线性查找。
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
           // 假如找到则持续填充到当时类的办法缓存中去
            log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
            imp = meth->imp;
            goto done;
        }
    }
} 

for 循环不断查找,找当时类的父类,直到当时类为 nil。

先在父类的办法缓存中查找(依据 sel & mask)cache_getImp ,找到则将办法写入到本身类的办法缓存中去 log_and_fill_cache(cls, imp, sel, inst, curClass);

比方 Person 类有 eat 办法,Student 类有 stduy 办法,调用 Student 目标的 eat 办法,则会走到这儿,从父类找到办法后写入到 Student 类的办法缓存中去。

假如在父类的办法缓存中没找到,则调用 getMethodNoSuper_nolock 父类的 办法数组(Array 元素为办法数组),依照排序好和没排序好别离走二分查找和线性查找。

假如找到则持续填充到当时类的办法缓存中去 log_and_fill_cache(cls, meth->imp, sel, inst, curClass);,最终 goto done

上面的流程是整个 objc_msgSend 的音讯发送阶段的整个流程。能够用下图表明

深度剖析 Runtime

动态办法解析阶段

接着检查源码

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                       bool initialize, bool cache, bool resolver)
{
    //...
    // No implementation found. Try method resolver once.
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }
    // ...
}
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

判别当时类没有走过动态办法解析阶段,则走动态办法解析阶段,调用 _class_resolveMethod 办法。

内部会判别但前类是不是元类目标、仍是类目标走不同逻辑。

类目标走 _class_resolveInstanceMethod 逻辑

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

中心就调用 bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); 运转 resolveInstanceMethod 办法。

元类目标走 _class_resolveClassMethod 逻辑

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);
    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

其实便是调用 bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_resolveClassMethod, sel);

最终仍是走到了 goto retry; 持续走完好的音讯发送流程(由于增加了办法,所以会依照办法查找再去履行的逻辑)

完好流程如下

深度剖析 Runtime

上 Demo

Person *person = [[Person alloc] init];
[person eat];

调用不存在办法则报错 ***** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person eat]: unrecognized selector sent to instance 0x101b2d900'**

由于调用目标不存在的办法,所以会 Crash

知道 objc_msgSend 的流程,咱们测验给它修正下

- (void)customEat {
    NSLog(@"我的假的 eat 办法,为了解决奔溃问题");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(eat)) {
        // 目标办法,存在于目标上。
        Method method = class_getInstanceMethod(self, @selector(customEat));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

也能够增加 c 语音办法

void customEat (id self, SEL _cmd) {
    NSLog(@"%@-%s-%s", self, sel_getName(_cmd), __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // 目标办法,存在于目标上。
        class_addMethod(self, sel, (IMP)customEat, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

由于 c 言语办法名便是函数地址,所以不需求直接传递即可,需求做下类型转换 (IMP)customEat

也能够给类办法做动态办法解析。需求留意的是类办法。

  • 调用 -(BOOL)resolveClassMethod:(SEL)sel

  • class_addMethod 办法中的第一个参数,需求加到类的元类目标中,所以是 object_getClass

Person *person = [[Person alloc] init];
[Person drink];
void customDrink (id self, SEL _cmd) {
    NSLog(@"假喝水");
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(drink)) {
        // 类办法,存在于元类目标上。
        class_addMethod(object_getClass(self), sel, (IMP)customDrink, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

音讯转发阶段

能走到音讯转发,阐明

  1. 类本身没有该办法(objc_msgSend 的音讯发送)

  2. objc_msgSend 动态办法解析失利或许没有做

阐明类本身和父类没有能够处理该音讯的才能,此刻应该将该音讯转发给其他目标。

检查 objc4 的源码

IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                       bool initialize, bool cache, bool resolver)
{
    //...
    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
    // ...
}

持续查找 _objc_msgForward_impcache

STATIC_ENTRY __objc_msgForward_impcache
MESSENGER_START
nop
MESSENGER_END_SLOW
// No stret specialization.
b    __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp    x17, __objc_forward_handler@PAGE
ldr    x17, [x17, __objc_forward_handler@PAGEOFF]
br    x1
END_ENTRY __objc_msgForward

查找 __objc_forward_handler 没有找到,能够猜测是一个 c 办法,去掉最前面的 _,依照 _objc_forward_handler 查找得到

__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

音讯转发的代码是不开源的,查找资料找到一份靠谱的 __forwarding 办法完成

为什么是 __forwarding__ 办法。咱们能够依据 Xcode 溃散窥视一二

深度剖析 Runtime

int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);
    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }
    // 调用 methodSignatureForSelector 获取办法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
            [receiver forwardInvocation:invocation];
            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }
    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    // The point of no return.
    kill(getpid(), 9);
}

详细地址能够参阅 __frowarding

完好流程如下

深度剖析 Runtime

上 Demo

Person 类不存在 drink 办法,Bird 类存在

@implementation Bird
- (void)drink
{
    NSLog(@"一只鸟儿在喝水");
}
@end
Person *person = [[Person alloc] init];
[person drink];

办法1

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(drink)) {
        return [[Bird alloc] init];
    } 
    return [super forwardingTargetForSelector:aSelector];
}
@end

办法2

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(drink)) {
        return nil;
    } 
    return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation invokeWithTarget:[[Bird alloc] init]];
}

留意:methodSignatureForSelector 假如回来 nil,则 forwardInvocation 不会履行

给 Person 类办法进行音讯转发处理

办法1

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(drink)) {
        return [Bird class];
    }
    return [super forwardingTargetForSelector:aSelector];
}

办法2

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(drink)) {
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(drink)) {
        return [[Bird class] methodSignatureForSelector:@selector(drink)];
    }
    return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation invokeWithTarget:[Bird class]];
}

办法签名的获取

办法1: 自己依据办法的回来值类型,办法2个根底参数参数:id selfSEL _cdm,其他参数类型依照 Encoding 自己拼。 相似 v16@0:8

办法2 :依据某个类的目标,去调用 methodSignatureForSelector 办法获取。

[[[Bird alloc] init] methodSignatureForSelector:**@selector**(drink)];

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(drink)) {
        return [[[Bird alloc] init] methodSignatureForSelector:@selector(drink)];
    }
    return [super methodSignatureForSelector:aSelector];
}

Super 原理

@implementation Person
@end
@implementation Student
- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"%@", [self class]);        // Student
        NSLog(@"%@", [self superclass]);   // Person 
        NSLog(@"%@", [super class]);       // Student
        NSLog(@"%@", [super superclass]);  // Person 
    }
    return self;
}
@end

后边2个的打印好像不符合预期?转成 c++ 代码看看

static instancetype _I_Student_init(Student * self, SEL _cmd) {
    if (self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_Student_91af5b_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_Student_91af5b_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_Student_91af5b_mi_2, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_ksvb7q252lbdfg78236t7tt00000gn_T_Student_91af5b_mi_3, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("superclass")));
    }
    return self;
}

[super class] 这句代码底层完成为 objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"));

__rw_objc_super 是什么?

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};

objc_msgSendSuper 如下

/**
 * Sends a message with a simple return value to the superclass of an instance of a class.
 * 
 * @param super A pointer to an \c objc_super data structure. Pass values identifying the
 *  context the message was sent to, including the instance of the class that is to receive the
 *  message and the superclass at which to start searching for the method implementation.
 * @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
 * @param ...
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method identified by \e op.
 * 
 * @see objc_msgSend
 */
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

所以 objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class")); 等同于下面代码

struct objc_super arg = {self, class_getSuperclass(self)};
objc_msgSendSuper(arg, sel_registerName("class"))

[super class] super 调用的 receiver 仍是 self

结构体的意图是为了在类目标查找的进程中,直接从当时类的父类中查找,而不是本类(比方 Student 类的 [super init] 会直接从 Person 的类目标中查找 init,找不到则经过 superclass 向上查找)

大致推测体系的 class、superclass 办法完成如下

@implementation Person
- (Class)class{
    return object_getClass(self);   
}
- (Class)superclass {
    return class_getSuperclass(object_getClass(self));
}
@end

class 办法是在 NSObject 类目标的办法列表中的。所以

[self class] 等价于 objc_msgSend(self, sel_registerName("class"))

[super class] 等价于 objc_msgSendSuper({self, class_getSuperclass(self)}, sel_registerName("class"))

其实2个办法实质上音讯 receiver 都是 self,也便是当时的 Student,所以打印都是 Student

定论:[super message] 有2个特征

  • super 音讯的调用者仍是 self

  • 办法查找是依据当时 self 的父类开端查找

经过将代码转为 c++ 发现,super 调用实质便是 objc_msgSendSuper,实践不然

咱们对 iOS 项目[super viewDidLoad] 下符号断点,发现objc_msgSendSuper2

深度剖析 Runtime

检查 objc4 源代码发现是一段汇编完成。

ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START
ldp    x0, x16, [x0]        // x0 = real receiver, x16 = class
ldr    x16, [x16, #SUPERCLASS]    // x16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2

所以 super viewDidLoad实质上便是

struct objc_super arg = {
    self, 
    [UIViewController class]    
};
objc_msgSendSuper2(arg, sel_registerName("viewDidLoad"));

objc_msgSendSuper2 和 objc_msgSendSuper 差异在于第二个参数

objc_msgSendSuper2 底层源码(汇编代码 objc-msg-arm64.s 422 行)会将第二个参数找到父类,然后进行办法缓存查找

objc_msgSendSuper 直接从第二个参数查找办法。

总结:clang 转 c++ 能够窥视体系完成,能够作为研究参阅。super 实质上便是 objc_msgSendSuper2,传递2个参数,第一个参数为结构体,第二个参数是sel。

为什么转为 c++ 和真正完成不相同?思考下

源代码变为机器码之前,会经过 LLVM 编译器转换为中心代码(Intermediate Representation),最终转为汇编、机器码

咱们来验证下 super 在中心码上是什么

clang -emit-llvm -S Student.m

llvm 中心码如下,能够看到确实内部是 objc_msgSendSuper2

; Function Attrs: noinline optnone ssp uwtable
define internal void @"\01-[Student sayHi]"(%0* %0, i8* %1) #1 {
  %3 = alloca %0*, align 8
  %4 = alloca i8*, align 8
  %5 = alloca %struct._objc_super, align 8
  store %0* %0, %0** %3, align 8
  store i8* %1, i8** %4, align 8
  %6 = load %0*, %0** %3, align 8
  %7 = bitcast %0* %6 to i8*
  %8 = getelementptr inbounds %struct._objc_super, %struct._objc_super* %5, i32 0, i32 0
  store i8* %7, i8** %8, align 8
  %9 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_SUP_REFS_$_", align 8
  %10 = bitcast %struct._class_t* %9 to i8*
  %11 = getelementptr inbounds %struct._objc_super, %struct._objc_super* %5, i32 0, i32 1
  store i8* %10, i8** %11, align 8
  %12 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.6, align 8, !invariant.load !12
  call void bitcast (i8* (%struct._objc_super*, i8*, ...)* @objc_msgSendSuper2 to void (%struct._objc_super*, i8*)*)(%struct._objc_super* %5, i8* %12)
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_.8 to i8*))
  ret void
}

指令介绍

@ - 全局变量
% - 局部变量
alloca - 在当时履行的函数的堆栈帧中分配内存,当该函数回来到其调用者时,将自动开释内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,store 写入
icmp - 两个整数值比较,回来布尔值
br - 选择分支,依据条件来转向label,不依据条件跳转的话相似 goto
label - 代码标签
call - 调用函数

isKindOfClass、isMemberOfClass

Demo

Student *student = [[Student alloc] init];
NSLog(@"%hhd", [student isMemberOfClass:[Student class]]); // 1
NSLog(@"%hhd", [student isKindOfClass:[Person class]]);    // 1
NSLog(@"%hhd", [Student isMemberOfClass:[Student class]]); // 0
NSLog(@"%hhd", [Student isKindOfClass:[Student class]]);    // 0

有些人答对了,有些人错了。

上面2个判别都是调用目标办法的 isMemberOfClassisKindOfClass

由于 objc4 是开源的,检查 object.mm

- (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 [self class] == cls;
}

isMemberOfClass 判别当时目标是不是传递进来的目标

isKindOfClass 内部是一个 for 循环,第一次循环先拿当时类的类目标,判别是不是和传递进来的目标相同,相同则 return YES,否则先给 tlcs 赋值当时类的父类,然后走第2次判别,直到 cls 不存在方位(NSObject 的父类为 nil)。所以 isKindOfClass 其实判别的是当时类是传递进来的类,或许传递进来类的子类

下面面2个判别都是调用类办法的 isMemberOfClassisKindOfClass

+ (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;
}

能够看到 +(BOOL)isMemberOfClass:(Class)cls 办法内部便是对当时类获取类目标,然后与传递进来的 cls 判别是否持平。由于是 [Student isMemberOfClass:[Student class]]) Student 类调用类办法 +isMemberOfClass 所以类目标的类目标也便是元类目标,cls 参数也便是 [Student class] 是一个类目标,元类目标等于类目标吗?显然不是

想让判别成立,能够改为 [Student isMemberOfClass:object_getClass([Student class])] 或许 [[Student **class**] isMemberOfClass:object_getClass([Student class])]

+(BOOL)isKindOfClass:(Class)cls 同理剖析。作用是当时类的元类,是否是右边传入目标的元类或许元类的子类。

来个特别 case

NSLog(@"%hhd", [[Student class] isKindOfClass:[NSObject class]]); // NO

输出 1。为什么?

看坐右边的部分,调用 isKindOfClass 办法,实质上便是 Student 类的类目标,也便是 Student 元类,和传入的右边 [NSObject class]判别是否想经过

第一次 for 循环当然不同,所以不能 return,会将 tcls 走步长改动逻辑 tcls = tcls->superclass,也便是找到当时 Student 元类目标的父类。

第2次 for 循环也相同不持平,Person 元类不等于 [NSObject class] 持续向上,直到 tcls = NSObject。此刻仍是不等,这时分 tcls 走步长改动逻辑,tcls = tcls->superclass NSObject 元类的 superclass 仍是 NSObject。所以 for 循环内部的判别编委 [NSObject class] == [NSObject class],return YES。

tips:基类的元类目标指向基类的类目标。

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

深度剖析 Runtime

Quiz

NSLog(@"%hhd", [NSObject isKindOfClass:[NSObject class]]);  // 1
NSLog(@"%hhd", [NSObject isMemberOfClass:[NSObject class]]);    //0
NSLog(@"%hhd", [Person isKindOfClass:[Person class]]);  // 0
NSLog(@"%hhd", [Person isMemberOfClass:[Person class]]);    //0

Runtime 刁钻题

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sayHi;
@end
@implementation Person
- (void)sayHi{
    NSLog(@"hi,my name is %@", self->_name); // hi,my name is 杭城小刘
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *temp = @"杭城小刘";
        id obj = [Person class];
        void *p = &obj;
        [(__bridge id)p sayHi];
        test();
    }
    return 0;
}

程序运转什么成果?

hi,my name is 杭城小刘

为什么会办法调用成功?为什么 name 打印出为 @”杭城小刘”

咱们来剖析下:

1.办法调用实质便是寻觅 isa 进行音讯发送

Person *person = [[Person alloc] init];
[person sayHi];

[[Person alloc] init]在内存中分配一块内存,然后 isa 指向这块内存,然后 person 指针,指向结构体,结构体的第一个成员。

2.栈空间数据内存向下成长。第一个变量地址高,其次降低。且每个变量的内存地址是连续的。

这个流程其实和上面的代码相同的。所以能够正常调用

void test () {
    long long a = 4;        // 0x7ff7bfeff2d8
    long long b = 5;        // 0x7ff7bfeff2d0
    long long c = 6;        // 0x7ff7bfeff2c8
    NSLog(@"%p %p %p", &a, &b, &c);
}

办法内的变量存储在栈上,堆向上增长,栈向下增长。

深度剖析 Runtime

3.实例目标的实质便是一个结构体,存储一切成员变量(isa 是一个特别成员变量,其他的成员变量,这儿便是 _name),sayHi 办法内部的 self 便是 obj,找成员变量的实质便是找内存地址的进程(此刻便是偏移8个字节)

上面代码能够类比类调用办法的流程。 obj 指针指向 Person 这块内存,给类目标发送 sayHi 音讯也便是经过 obj 指针找到 isa,刚好 obj 指针指向的地址便是类目标的类结构体的地址,结构体成员变量第一个便是 isa 指针,结构体的其他成员变量便是类的其他特点,这儿也便是 _name,所以咱们给自定义的指针 void *p 调用 sayHi 办法,体系 runtime 在打印 name 的时分,会在 p 附近(下8个字节,由于 isa 是指针,长度为8)找 _name 特点,此刻也就找到了 temp 字符串。

struct Person_IMPL {
    Class isa; // 8字节
    NSString *_name;    // 8字节
}

再看一个变体1

NSObject *temp = [[NSObject alloc] init];
id obj = [Person class];
void *p = &obj;
[(__bridge id)p sayHi];
// hi,my name is <NSObject: 0x101129d60>

再看一个变体2(将代码放在 ViewController中)

- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [Person class];
    void *p = &class;
    NSObject *temp = [[NSObject alloc] init];
    [(__bridge id)p sayHi];
}
// hi,my name is <ViewController: 0x7fe246204fd0>

搞懂的小伙伴不利诱了。没搞懂其实便是没搞懂栈地址由高到低,向下成长super 调用的实质。

再强调一句,依据指针寻觅成员变量 _name 的进程其实便是依据内存偏移找目标的进程。在变体2中,isa 地址便是 class 的地址,所以依照地址 +8 的战略,其实前一个局部变量。

[super viewDidLoad]; 实质便是 objc_msgSendSuper({self, class_getSuperclass(self)}, sel_registerName("viewDidLoad"))

struct objc_super arg = {self, class_getSuperclass(self)};
objc_msgSendSuper(arg, sel_registerName("viewDidLoad"));

所以此刻的“前一个局部变量” 也便是结构体 objc_super 类型的 arg。arg 是一个结构体,结构体第一个成员变量便是 self,所以“前一个局部变量” 也便是 self(ViewController)

深度剖析 Runtime

运用场景

1.统计 App 中未呼应的办法。给 NSObject 增加分类

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    // 原本能调用的办法
    if ([self respondsToSelector:aSelector]) {
        return [super methodSignatureForSelector:aSelector];
    }
    // 找不到的办法
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 找不到的办法,都会来到这儿
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"找不到%@办法", NSStringFromSelector(anInvocation.selector));
}
@end

2.修正类的 isa

object_setClass 完成

Person *p = [Person new];
object_setClass(p, [Student class]);

深度剖析 Runtime

3.动态创建类

objc_allocateClassPairobjc_registerClassPair 成对存在

动态创建类、增加特点、办法

void study (id self, SEL _cmd) {
    NSLog(@"在学习了");
}
void createClass (void) {
    Class newClass = objc_allocateClassPair([NSObject class], "GoodStudent", 0);
    class_addIvar(newClass, "_score", 4, 1, "i");
    class_addIvar(newClass, "_height", 4, 1, "i");
    class_addMethod(newClass, @selector(study), (IMP)study, "v16@0:8");
    objc_registerClassPair(newClass);
    id student = [[newClass alloc] init];
    [student setValue:@100 forKey:@"_score"];
    [student setValue:@177 forKey:@"_height"];
    [student performSelector:@selector(study)];
    NSLog(@"%@ %@", [student valueForKey:@"_score"], [student valueForKey:@"_height"]);
}

深度剖析 Runtime

runtime 中 copy、create 等出来的内存,不运用的时分需求手动开释objc_disposeClassPair(newClass>)

4.拜访成员变量信息

void ivarInfo (void) {
    Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
    NSLog(@"%s %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar)); //_name @"NSString"
    // 设置、获取成员变量
    Person *p = [[Person alloc] init];
    Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
    object_setIvar(p, ageIvar, (__bridge id)(void *)27);
    NSLog(@"%d", p.age);
}

runtime 设置值 api object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 第三个参数要求为 id 类型,但是咱们给 int 类型的特点设置值,怎么办?能够将27这个数字的地址传进去,一起需求类型转换为 id (__bridge id)(void *)27)

KVC 能够依据详细的值,去取出 NSNumber ,然后调用 intValue

[p setValue:@27 forKey:@"_age"];

5.拜访目标的一切成员变量信息

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
unsigned int count;
// 数组指针
Ivar *properties = class_copyIvarList([Person class], &count);
for (int i =0 ; i<count; i++) {
    Ivar property = properties[i];
    NSLog(@"特点名称:%s, 特点类型:%s", ivar_getName(property), ivar_getTypeEncoding(property));
}
free(properties);
//特点名称:_age, 特点类型:i
// 特点名称:_name, 特点类型:@"NSString"

依据这个能够做许多工作,比方设置解模型、给 UITextField 设 placeholder 的色彩

先依据 class_copyIvarList 拜访到 UITextFiled 有许多特点,然后找到可疑累_placeholderLabel,经过打印 class、superclass 得到类型为 UILabel。所以用 UILabel 目标设置 color 即可,要么经过 KVC 直接设置

[self.textFiled setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];

或许设置字典转模型(不行强健,随便写的。详细能够参阅 YYModel)

+ (instancetype)lbp_modelWithDic:(NSDictionary *)dict
{
    id obj = [[self alloc] init];
    unsigned int count;
    Ivar *properties = class_copyIvarList([self class], &count);
    for (int i =0 ; i<count; i++) {
        Ivar property = properties[i];
        NSString *keyName = [[NSString stringWithUTF8String:ivar_getName(property)] stringByReplacingOccurrencesOfString:@"_" withString:@""];
        id value = [dict objectForKey:keyName];
        [self setValue:value forKey:keyName];
    }
    free(properties);
    return obj;
}

6.替换办法完成

留意

  • 相似 NSMutableArray 的时分,+load 办法进行办法替换的时分需求留意类簇的存在,比方 __NSArrayM

  • 办法交流一般写在类的 +load 办法中,且为了避免出问题,比方他人手动调用 load,代码需求加 dispatch_once

void studentSayHi (void) {
    NSLog(@"Student say hi");
}
void changeMethodImpl (void){
    class_replaceMethod([Person class], @selector(sayHi), (IMP)studentSayHi, "v16@0:8");
    Person *p = [[Person alloc] init];
    [p sayHi];
}
// Student say hi

上述代码能够换一种写法

class_replaceMethod([Person class], @selector(sayHi), imp_implementationWithBlock(^{
    NSLog(@"Student say hi");
}), "v16@0:8");
Person *p = [[Person alloc] init];
[p sayHi];

imp_implementationWithBlock(id _Nonnull block) 该办法将办法完成替换为包装好的 block

Person *p = [[Person alloc] init];
Method sleep = class_getInstanceMethod([Person class], @selector(sleep));
Method sayHi = class_getInstanceMethod([Person class], @selector(sayHi));
method_exchangeImplementations(sleep, sayHi);
[p sayHi];    // 人生无常,抓紧睡觉
[p sleep];    // Person sayHi

7.无痕埋点

对 App 内一切的按钮点击事情进行监听并上报。发现 UIButton 承继自 UIControl,所以增加分类,在 load 办法内,替换办法完成。UIControl 存在办法 sendAction:to:forEvent:

@implementation UIControl (Monitor)
+ (void)load {
    Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method method2 = class_getInstanceMethod(self, @selector(lbp_sendAction:to:forEvent:));
    method_exchangeImplementations(method1, method2);
}
- (void)lbp_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
    // 调用体系本来的完成
    [self mj_sendAction:action to:target forEvent:event];
//    [target performSelector:action];
}
@end

为了对事务代码无影响,在 hook 代码内部又要调用回去,所以需求调用本来的办法,此刻由于交流办法完成,所以本来的办法应该是 lbp_sendAction:to:forEvent:

method_exchangeImplementations 办法完成交流了,体系会清空缓存,调用 flushCaches 办法,内部调用 cache_erase_nolock 来清空办法缓存。

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;
    rwlock_writer_t lock(runtimeLock);
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;
    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?
    flushCaches(nil);
    updateCustomRR_AWZ(nil, m1);
    updateCustomRR_AWZ(nil, m2);
}
static void flushCaches(Class cls)
{
    runtimeLock.assertWriting();
    mutex_locker_t lock(cacheUpdateLock);
    if (cls) {
        foreach_realized_class_and_subclass(cls, ^(Class c){
            cache_erase_nolock(c);
        });
    }
    else {
        foreach_realized_class_and_metaclass(^(Class c){
            cache_erase_nolock(c);
        });
    }
}

总结:

OC 是一门动态性很强的编程言语,允许许多操作推迟到程序运转时决议。OC 动态性其实便是由 Runtime 来完成的,Runtime 是一套 c 言语 api,封装了许多动态性相关函数。平时写的 oc 代码,底层大多都是转换为 Runtime api 进行调用的。

  • 相关目标
  • 遍历类的一切成员变量(能够拜访私有变量,比方修正 UITextFiled 的 placeholder 色彩、字典转模型、自动归档接档)
  • 交流办法完成
  • 扩展点击区域
  • 运用音讯转发机制,解决音讯找不到的问题
  • 无痕埋点
  • 热修复(热修复计划有几大类:内置虚拟机、下发脚本相关到 runtime 修正原始行为、AST 解析处理)
  • 安全气垫(运用场景褒贬不一:比方责任边界问题、尽管兜住了 crash,但是问题没有充沛露出。一个优雅的战略是线上兜住 crash,但是一起收集案发数据,走事务异常报警,开发立马去依据数据剖析这个问题是事务异常仍是什么状况,要不要发布热修,仍是后端数据/逻辑错误)