IOS基础知识-weak原理篇

2019-06-24  本文已影响0人  程序员的自我救赎
weak的作用是弱引用,修饰的对象引用计数不会+1;在对象被释放后,对象会被置为nil;

weak底层原理

首先需要看下weak编译后的实现,比如有如下代码:

int main(){
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}

在通过clang编译后,代码实现如下:

int main(){
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    id __attribute__((objc_ownership(weak))) obj1 = obj;
}

编译后的weak,通过objc_ownership实现weak方法:即获取对象所有权,是对对象weak初始化的一个操作

weak的实现原理:

由上可知,weak是基于runtime来实现的(objc_msgSend);
weak和strong都是Objective-C的修饰符,strong是由runtime维护的一个自动计数表结构,而weak是有runtime维护的weak表;
在runtime的源码中,可以找到objc_weak.h和objc_weak.m文件;而且在objc_weak.h的头文件中可以找到weak表的结构定义

weak表

下面的代码是runtime中的定义:

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries; //保存了所有指向指定对象的weak指针   weak_entries的对象
    size_t    num_entries;              // weak对象的存储空间
    uintptr_t mask;                      //参与判断引用计数辅助量
    uintptr_t max_hash_displacement;    //hash key 最大偏移值
};

weak_table_t 是一个全局的weak引用表,使用不定类型的地址作为key,weak_entry_t 作为value,是一个存储在弱引用表中的内部结构体,它负责维护和存储指向一个对象的所有弱引用的hash表;其定义如下:

typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;  //范型
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    }
}

在 weak_entry_t 的结构中,DisguisedPtr referent 是对泛型对象的指针做了一个封装,通过这个泛型类来解决内存泄漏的问题。从注释中写 out_of_line 成员为最低有效位,当其为0的时候, weak_referrer_t 成员将扩展为多行静态 hash table。其实其中的 weak_referrer_t 是二维 objc_object 的别名,通过一个二维指针地址偏移,用下标作为 hash 的 key,做成了一个弱引用散列。

out_of_line:最低有效位,也是标志位。当标志位 0 时,增加引用表指针纬度。
num_refs:引用数值。这里记录弱引用表中引用有效数字,因为弱引用表使用的是静态 hash 结构,所以需要使用变量来记录数目。
mask:计数辅助量。
max_hash_displacement:hash 元素上限阀值。
其实 out_of_line 的值通常情况下是等于零的,所以弱引用表总是一个 objc_objective 指针二维数组。一维 objc_objective 指针可构成一张弱引用散列表,通过第三纬度实现了多张散列表,并且表数量为 WEAK_INLINE_COUNT 。

总之:

weak_table_t:weak全局表,采用hash(哈希表)的方式存储weak引用对象
weak_entry_t:weak引用对象,即weak_table_t中hash表的value
objc_object:标记weak对象,即weak_entry_t中的泛型对象

weak的释放为nil的过程

a、调用objc_release释放
b、因为引用计数为0,则调用对象的dealloc->objc_rootDealloc->objc_dispose
c、调用objc_destructInstance
d、最后调用objc_clear_deallocating
objc_clear_deallocating该函数的动作如下:
1、从weak表中获取废弃对象的地址为键值的记录
2、将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
3、将weak表中该记录删除
4、从引用计数表中删除废弃对象的地址为键值的记录
其实Weak表是一个hash(哈希)表,然后里面的key是指向对象的地址,Value是Weak指针的地址的数组。

总结

weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 storeWeak() 函数, storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

上一篇下一篇

猜你喜欢

热点阅读