洋仔的博客 洋仔的博客
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

洋仔

奋斗的小青年
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • iOS基础知识

    • iOS底层相关

    • Runloop系列

    • Runtime系列

    • 内存管理系列

      • weak的实现原理
        • weak基本用法
        • weak 实现原理的概括
        • weak 实现原理详细介绍
          • objc_initWeak分析
        • 弱引用清除流程
      • 内存管理系列
      • autoReleasePool
      • 循环引用
      • 内存管理自我总结
    • Block系列

    • 线程系列

    • KVC跟KVO系列以及通知中心

    • UI系列

    • 离屏渲染系列

    • 组件化系列跟架构

    • OC跟webview交互系列

    • 持久化系列

    • APP编译系列

    • APP性能优化系列

    • cocoapods系列

    • swift系列

    • Git系列

    • 网络相关

    • 三方库系列

    • 系统原理

    • 总结系列

    • 算法系列

    • 数据结构系列

  • 前端

  • 技术
  • iOS基础知识
  • 内存管理系列
洋仔
2023-08-11
目录

weak的实现原理

# weak基本用法

weak是弱引用,用weak来修饰、描述所引用对象的计数器并不会增加,而且weak会在引用对象被释放的时候自动置为nil,这也就避免了野指针访问坏内存而引起奔溃的情况,另外weak也可以解决循环引用。

拓展:为什么修饰代理使用weak而不是用assign? assign可用来修饰基本数据类型,也可修饰OC的对象,但如果用 assign修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为nil,否则会产生野指针,如果还通过此指针操作那块内存,会导致EXC_BAD_ACCESS错误,调用了已经被释放的内存空间;而weak只能用来修饰OC对象,而且相比assign比较安全,如果指向的对象消失了,那么它会自动置为nil,不会导致野指针。

# weak 实现原理的概括

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址,就是地址的地址)集合(当weak指针的数量小于等于4时,是数组, 超过时,会变成hash表)。

weak 的实现原理可以概括以下三步:

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

# weak 实现原理详细介绍

示例代码

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}
1
2
3
4

一般情况下,我们不会直接用__weak来修饰一个刚创建出来的临时变量,因为__weak修饰这个变量,运行时一创建出来就会释放,而如果使用一个临时变量objc1话,创建出来后会放到自动释放池中,延缓这个变量的生命周期,变量的生命周期会跟着自动释放池自动保持,所以这样能够保证不会一创建出来就会释放。

# objc_initWeak分析

在libobjc.dylib源码中成功定位到了初始化过程,同时objc_initWeak传入了两个参数,location即弱引用的地址(存储在栈中),newObjc也就是创建的对象(存储在堆中)。见下图:

Screenshot-2023-08-21-at-22

首先会判断对象是否为空,如果为空直接返回nil。如果不为空,则会调用stroeWeak方法进行存储。location是弱引用的地址;newObjc是一个对象,在底层对象的实现就是objc_object。 总结:object必须是一个没有被注册为__weak对象的有效指针。

# 数据结构分析

在进行方法分析之前,我们需要先摸清楚数据存储结构。系统维护了一个SideTables,那么SideTable散列表为什么有多张?一张表不安全,太多了性能不好。真机下8张表,其他环境下64张,散列表也是一张hash表。而SideTables是一个hash表,综合了链表和数组的特点。拉链法,同一个hash可以放在同一个表中。

  • SideTable
    struct SideTable {
        // 自旋锁
        spinlock_t slock;
        // 引用计数
        RefcountMap refcnts;
        // 对象的弱引用表
        weak_table_t weak_table;
    }
1
2
3
4
5
6
7
8
  • 弱引用表weak_table

      struct weak_table_t {
         // 保存了所有指向指定对象的 weak 指针
         weak_entry_t *weak_entries;
         // 存储空间
         size_t    num_entries;
         // hash算法辅助值
         uintptr_t mask;
         // 最大偏移量
         uintptr_t max_hash_displacement;
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    weak表是一个弱引用表,实现为一个weak_table_t结构体,存储了某个对象相关的所有的弱引用信息,这是一个hash表。使用不定类型对象的地址作为key,用weak_entry_t类型结构体对象作为value。其中的weak_entries成员,即为弱引用表入口。

  • weak_entry_t

    struct weak_entry_t {
       DisguisedPtr<objc_object> referent;
       union {
           struct {
               weak_referrer_t *referrers;
               uintptr_t        out_of_line_ness : 2;
               uintptr_t        num_refs : PTR_MINUS_2;
               uintptr_t        mask;
               uintptr_t        max_hash_displacement;
           };
           struct {
               // out_of_line_ness field is low bits of inline_referrers[1]
               weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
           };
        }
     }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

弱引用实体,有两个属性,一个对象,另一个属性是一个联合体,其中包含弱引用的数组。

  • objc_storeWeak分析 该方法整体上可以分为三个功能,见下图:

Screenshot-2023-08-21-at-22

对当前引用的数据处理判断,判断该引用是否存在旧值以及是否指向了新值;如果引用当前有指向的值,即存在旧值,则需要将旧值清除;同时如果引用指向了新的对象,即存在新值,则需要进行对应对象弱引用的初始化工作。 我们从弱引用指向一个新值开始探索,整理其核心代码如下:

    // 根据对象获取其对应的散列表
    SideTables *newTable = &SideTables()[newObj];
    // 从散列表中获取弱引用表,并传入
    newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
1
2
3
4

进入weak_register_no_lock流程。见下图:

Screenshot-2023-08-21-at-22

方法所传入的前三个参数分别为:弱引用表、对象、弱引用。该方法会判断对象是否析构,如果有就不会处理,直接返回。如果没有,则会通过weak_entry_for_referent方法获取对象对应的weak_entry_t见下图

Screenshot-2023-08-21-at-22

通过hash函数获取其表中的下标,通过循环弱引用表,找对应的下标,获取对应的weak_entry_t。获取weak_entry_t后,去存储新的弱引用对象,回到方法weak_register_no_lock,在调用weak_entry_for_referent之后,会通过append_referrer(entry, referrer);方法进行弱引用的存储。见下图:

Screenshot-2023-08-21-at-22

  • 总结:

提供维护了一个SideTables,其中多张散列表SideTable,每一张SideTable表中有自旋锁、引用计数表、弱引用表weak_table_t。弱引用表中存储一些实体weak_entry_t,实体中包括了对象和弱引用数组。

弱引用存在旧值

上面分析了弱引用指向一个新值的处理流程,那如果弱引用存在有指向的旧值,怎么处理呢?也即是storeWeak方法的下面这段代码:

    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
1
2
3

进入weak_unregister_no_lock方法,见下图:

Screenshot-2023-08-21-at-22

在此过程中,会获取弱应用对象对应的实体weak_entry,并调用remove_referrer方法,从实体中移除对应的弱引用。见下图:

Screenshot-2023-08-21-at-22

此过程会从对象的引用列表移除该的引用,并将弱引用个数减1。

# 弱引用清除流程

当一个对象调用dealloc方法时,其弱引用处理流程见下面代码:

点击查看
        - (void)dealloc {
            _objc_rootDealloc(self);
        }

        void _objc_rootDealloc(id obj)
        {
            ASSERT(obj);
            obj->rootDealloc();
        }

        inline void objc_object::rootDealloc()
        {
            if (isTaggedPointer()) return;  // fixme necessary?
            if (fastpath(isa.nonpointer                     &&
                         !isa.weakly_referenced             &&
                         !isa.has_assoc                     &&
        #if ISA_HAS_CXX_DTOR_BIT
                         !isa.has_cxx_dtor                  &&
        #else
                         !isa.getClass(false)->hasCxxDtor() &&
        #endif
                         !isa.has_sidetable_rc))
            {
                assert(!sidetable_present());
                free(this);
            } 
            else {
                object_dispose((id)this);
            }
        }

        id object_dispose(id obj)
        {
            if (!obj) return nil;
            objc_destructInstance(obj);    
            free(obj);
            return 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, /*deallocating*/true);
                obj->clearDeallocating();
            }
            return obj;
        }

        inline void objc_object::clearDeallocating()
        {
            if (slowpath(!isa.nonpointer)) {
                // 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.
                clearDeallocating_slow();
            }
            assert(!sidetable_present());
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

最终会调用clearDeallocating方法对弱引用进行处理。针对有弱引用的对象,会调用clearDeallocating_slow();方法,最终的弱引用清除的处理流程在weak_clear_no_lock中。见下图:

Screenshot-2023-08-21-at-22

在此过程中,首先在弱引用表中获取对象对应的实体,开启循环,将数组中的弱引用全部设为nil,最后将实体从弱引用表中移除。

附弱引用存储的数据结构

Screenshot-2023-08-21-at-22

编辑 (opens new window)
上次更新: 2024/10/23, 23:26:17
RunTime系列
内存管理系列

← RunTime系列 内存管理系列→

最近更新
01
数组
10-25
02
数组双指针系列之对撞指针
10-25
03
数组双指针系列之快慢指针
10-25
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式