iOS 底层 内存管理之weak指针 - AlvinSunny/OC-TheUnderlying GitHub Wiki

先让我们来看看SideTable的结构图

SideTable的结构图.png

weak指针底层实现源码分析

需要通过dealloc的内部实现,查看对象释放过程来了解; 目的是为了找到 weak_clear_no_lock函数 的实现

  • 如果赶时间可以直接到c++文件 objc-weak.mm 中搜索🔍weak_clear_no_lock函数

NSObject.mm


1.  第一步
- (void)dealloc {
    _objc_rootDealloc(self);
}

2. 第二步
inline void  objc_object::rootDealloc()
{
    //是否是tagged pointer指针 是直接return,不需要释放
    if (isTaggedPointer()) return;

    if (fastpath(isa.nonpointer  &&          //优化过的isa
                 !isa.weakly_referenced  &&  //没有被弱引用指向过
                 !isa.has_assoc  &&          //没有设置过关联对象
                 !isa.has_cxx_dtor  &&       //没有用到析构函数
                 !isa.has_sidetable_rc))     //引用计数没有过大
    {
        assert(!sidetable_present());        //全局的引用计数散列表也没有
        free(this);                          //直接释放
    } 
    else {//优化过的isa,但其他条件不满足
        object_dispose((id)this);
    }
}

3. 第三步
id object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    //销毁实例对象
    free(obj);//释放
    return nil;
}

4. 第四步
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor(); //是否有c++析构函数
        bool assoc = obj->hasAssociatedObjects();//是否有设置关联对象

        // This order is important.有就销毁它们
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        //开始回收内存
        obj->clearDeallocating();
        
    }

    return obj;
}

5.  重点来了 😁😁😁😁😁😁😁😁😁
inline void objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {//没有被优化过的ISA
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        
        // Slow path for non-pointer isa with weak refs and/or side table data.
        
        /*
       优化过的ISA,有weak指针指向或者引用计数存储在sideTable中,
       那就找到它消灭它
       */
        clearDeallocating_slow();
        
    }

    assert(!sidetable_present());
}

6.   😜😜😜😜😜😜😜😜😜😜😜😜😜😜😜😜

 关于weak引用的底层实现入口方法:weak_clear_no_lock 

NEVER_INLINE void objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    //获取当前obj对应的SideTable
    SideTable& table = SideTables()[this];
    //加锁
    table.lock();
    
    //有weak指针指向
    if (isa.weakly_referenced) {
        //传入弱引用表和当前对象,清空
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    
    //有引用计数存储在sidetable中
    if (isa.has_sidetable_rc) {
        //传入当前对象,擦除存储
        table.refcnts.erase(this);
    }
    
    //解锁
    table.unlock();
}

weak_clear_no_lock函数 的实现


void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    //1. 获取当前对象
    objc_object *referent = (objc_object *)referent_id;
    //2. 传入全局弱引用表和当前对象,取出weak_entry_t
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    //3. 以下处理目的 : 使弱引用归 0
    
    //3.1 定义弱引用持有者数组
    weak_referrer_t *referrers;
    //数量
    size_t count;
    
    //3.2 找到弱引用持有者放进数组
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    //3.3 循环取出弱引用持有者赋值为 nil
    for (size_t i = 0; i < count; ++i) {
        
        objc_object **referrer = referrers[i];
        
        if (referrer) {
            
            if (*referrer == referent) {
                *referrer = nil;
            }
            
            else if (*referrer) {
                //有弱引用错误, weak持有不合法
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
                
            }
            
        }
    }
    
    //4. 移除entry
    weak_entry_remove(weak_table, entry);
    
}

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;
    
    /*
     hash_pointer:获取当前对象地址值 按位&上 弱引用表的 mask值,
     最终得到索引-- >index
    */
    size_t begin = hash_pointer(referent) & weak_table->mask;
    size_t index = begin;
    
    //从0开始位移
    size_t hash_displacement = 0;
    
    //弱引用哈希表中的对象不等于当前对象就不停止查找,
    //简单来说就是: 找到弱引用指针存储在引用表中的位置
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    //取出weak_entry_t
    return &weak_table->weak_entries[index];
}

总结: 在确定有弱引用时

  • 第一步:调用weak_entry_for_referent函数,传入全局弱引用表当前对象;获取weak_entry_t
  • 第二步:从weak_entry_treferrers属性 中找到弱引用持有者放进数组
  • 第三步:for循环取出弱引用持有者赋值为 nil
  • 第四步:调用 weak_entry_remove函数,从全局弱引用表中移除当前entry

初始化弱引用图:

初始化弱引用图.png

__weak__unsafe_unretained 的区别是什么 ?assignn呢 ?

  • 共同点: __weak __unsafe_unretained都是弱引用指针,都不会对所引用的对象产生强引用
  • 区别是: __weak 指向的对象销毁时,会自动让指针置为 nil __unsafe_unretained 是不安全的 ,指向的对象销毁时,ARC并不会对它做任何处理,这时 __unsafe_unretained 中存储着一个被释放的对象的地址
  • 如果这个时候刚好向__unsafe_unretained 指针指向的对象发消息,那就会造成Crash. 也就是常见的野指针(Thread 1: EXC_BAD_ACCESS(code=1,address=0x228a2fa5c578))如下图

image

  • assign: 多用来修饰基本数据类型
  • assign关键字修饰的数据在 setter方法不会生成 相关的内存管理代码
  • assign关键字修饰的对象释放后,指针不会被清空 依然指向释放前的对象;这很危险,在后续的操作中如果用到该对象会出现坏内存访问导致崩溃,所以不会用来修饰OC对象

__weak 和 weak

  • __weak用于修饰指针变量, weak用于属性声明OC对象
  • __weak 指向的对象销毁时,会自动让指针置为nil;weak指向的对象一旦释放了,ARC会及时通过运行时从该对象的弱引用表中找到weak指针并移除

总结

嫌上面的内容太多,那就看简化版的

  • runtime维护了一个weak表,用于存储指向某个对象的所有weak指针
  • weak表其实是一个hash(哈希)表,key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

稍微详细版

  • 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  • 添加引用时:objc_initWeak函数会调用 -- > storeWeak() 函数, storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  • 释放时: 调用clearDeallocating函数。 clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中移除,最后清理对象的记录。
小尾巴

大家可能看了这篇文章还是晕乎乎的,主要是对里面的数据结构不是很清楚,我又找到2篇很好的文章,看完这个,保管完全不会晕乎乎了。

iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-一

iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-二