【iOS】Runtime


动态性

Objective-C是一门动态性比较强的编程语言,跟CC++等语言有着很大的不同,其动态性是由Runtime API来支撑的
Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

什么是Runtime?

OC是一门动态性很强的编程语言,不像C/C++等编译型语言,程序运行结果就是编译后的结果,OC允许程序在运行时动态地去修改一些东西,比如动态添加方法、动态替换方法的实现、动态地去修改实例对象的类型,也就是允许这些操作推迟到运行时,这种动态性就是由Runtime来支撑和提供的
Runtime就是一套C语言API,封装了很多动态性相关的函数,平时编写的OC中动态相关的代码底层都会转换成Runtime API进行调用

底层相关数据结构

isa结构

ARM64架构之前,isa只是一个普通的指针,存储着classmeta-class对象的地址;之后,对isa进行了优化,其结构采用了union联合体,将一个64位的内存数据bits分开存储了许多数据,其中的33位才是存储class或meta-class的地址值

在这里插入图片描述

优化过的isa其实就是位域,进一步了解isa结构见这篇文章:【iOS】OC类与对象的本质分析

在这里插入图片描述

Class结构

在这里插入图片描述

class_rw_t

Class对象的底层结构objc_class中,我们知道通过bits & FAST_DATA_MASK就可以得到class_rw_t类型的表结构

在这里插入图片描述

class_rw_t里面的methodspropertiesprotocols都是二维数组,是可读可写的,包含了类的初始内容分类的内容

method_array_t举例,里面的元素都是method_list_t类型的二维数组,每一个二维数组又是method_t类型的元素,表示每一个方法类型

class_ro_t

在这里插入图片描述

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

objc源码里的头文件objc-runtime-new.mm,通过查看函数realizeClassWithoutSwift的实现,程序运行时会将class_ro_t里面的数据和分类里面的数据信息全部合并到一起放到class_rw_t里面来

method_t

method_t是对函数的封装,里面包含了函数名编码信息以及函数地址

在这里插入图片描述
IMP代表函数的具体实现,指向着该函数的地址

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

SEL代表函数名,一般叫做选择器,底层结构跟char *类似,可以通过@selector()sel_registerName()获得

typedef struct objc_selector *SEL;
// 不同类中相同名字的方法,所对应的方法选择器是相同的,可以通过sel_getName()和NSStringFromSelector()转成字符串

types包含了函数返回值、参数编码的字符串,排列顺序如下

在这里插入图片描述

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码

在这里插入图片描述

一个函数默认会带有两个参数id _NonnullSEL _Nonnull,之后才是写入的参数
下面举例说明,函数的types是多少

- (int)test:(int)age height:(float)height
{
   
    NSLog(@"%s", __func__);
    return 0;
}

// 该函数types为i24@0:8i16f20
// i 返回值int类型
// 24 几个返回值类型占据的大小总和(8 + 8 + 4 + 4)
// @ id类型
// 0 表示从第0位开始
// : SEL类型
// 8 从第8位开始
// i 参数int类型
// 16 从第16位开始
// f 参数float类型
// 20 从第20位开始

// 可以通过输出日志验证
NSLog(@"%s %s %s", @encode(unsigned short), @encode(Class), @encode(SEL));

方法缓存cache_t

Class内部结构中有个方法缓存cache_t,用散列表(哈希表) 来缓存曾经调用过的方法,可以提高方法的查找速度

在这里插入图片描述

objc-cache.mm文件里可以查看cache_t::insert函数,是通过一套哈希算法计算出索引,然后根据索引在散列表数组里直接插入数据进行缓存

void cache_t::insert(SEL sel, IMP imp, id receiver) {
   

    ....
    
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    
//  ....

    else {
   
        // 如果空间已满,那么就进行扩容,乘以2倍
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
   
            capacity = MAX_CACHE_SIZE;
        }
        
        // 将旧的缓存释放,清空缓存,然后设置最新的mask值
        reallocate(oldCapacity, capacity, true);
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    
    // 通过 sel&mask 计算出索引(哈希算法)
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    do {
   
        // 通过索引找到的该SEL为空,那么就插入bucket_t
        if (fastpath(b[i].sel() == 0)) {
   
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        
        // 用索引从bucket里面取sel和传进来的sel做比较,如果一样证明已经存有,直接返回
        if (b[i].sel() == sel) {
   
            return;
        }
        
        // 从散列表里查找,如果上述条件不成立(索引冲突),那么通过cache_next计算出新的索引再查找插入
    } while (fastpath((i = cache_next(i, m)) != begin));

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
清空缓存cache_t::reallocate

当存储空间已满时,会进行扩容,并且将旧的缓存全部释放清空,然后设置最新的mask值,mask值是散列表的存储容量-1,也正好对应散列表的索引值

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) {
   
    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    ASSERT(newCapacity > 0);
    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
   
    // 将旧的缓存和mask释放
    if (freeOld) {
   
        collect_free(oldBuckets, oldCapacity);
    }
}
哈希算法cache_hash
static inline mask_t cache_hash(SEL sel, mask_t mask) {
   
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
    // 与mask按位与,有时也可对mask取余%
}

如果计算出的索引在散列表中已经有了缓存数据,那么就通过cache_next更新下索引值,再去对应的位置插入缓存数据

更新索引值cache_next

通过源码可以看到计算方式如下:

#if CACHE_END
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值