闲聊Objective-C中的weak

前言

在objective-c中,weak几乎无处不在。尤其是定义ivar时,经常要用到这个关键字。用weak修饰的变量,在对象释放之后,对象会自动置为nil。它是怎么做到的呢?在研究它之前,有几个词有必要先了解一下。

关键词

SideTable

SideTable是一个结构体,主要有三个成员。它的作用就是用来管理对象的weak表,引用计数在此先不表。

struct SideTable {
    // 自旋锁
    spinlock_t slock;
    // 引用计数的hash表
    RefcountMap refcnts;
    // 存储对象弱引用指针的hash表
    weak_table_t weak_table;

    ...
}

其中weak表(weak_table)是实现weak功能的核心数据结构。其实也可以理解SideTable是对weak_table的一个封装,方便更好的使用和访问weak表。

weak表

weak表是一个hash表,存储了弱引用对象以及相关的所有弱引用的信息。key为对象的地址,value为指向该对象的weak指针地址数组。

/**
 * 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;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

抽象一点的话可以像下面这么理解:

f(对象的地址) = 指向该对象的weak指针地址数组

weak_entry_t

weak_entries也是一个hash结构体,它存储的是指向绑定的弱引用对象的所有weak指针的地址。


/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
 * is instead a small inline array.
 */
#define WEAK_INLINE_COUNT 4

// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.
#define REFERRERS_OUT_OF_LINE 2

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];
        };
    };

    ...
};

weak的实现原理

闲聊Objective-C中的weak

  • ①初始化时,runtime调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  • ②objc_initWeak函数会调用storeWeak函数。更新指针指向,创建弱引用表。
  • ③如果weak指针之前指向了一个弱引用对象,则调用weak_unregister_no_lock函数接触该对象和weak指针的绑定。
  • ④如果weak指针需要指向一个新的弱引用对象,则调用weak_register_no_lock函数把weak指针地址添加到以该对象的地址为key的弱引用表里,从而实现绑定。

释放

闲聊Objective-C中的weak

  • ①对象释放时调用release函数。
  • ②然后调_objc_rootRelease函数。
  • ③接着调rootRelease函数。
  • ④引用计数为0时,执行dealloc函数。
  • ⑤然后调_objc_rootDealloc函数。
  • ⑥接着调rootDealloc函数。
  • ⑦接着调object_dispose函数。
  • ⑧接着调用objc_destructInstance函数。
  • ⑨接着就是一个比较重要的函数clearDeallocating。
  • ⑩接着调用clearDeallocating_slow函数。

clearDeallocating中有两个分支,现实判断对象是否采用了优化isa引用计数,如果没有的话则需要清理对象存储在SideTable中的引用计数数据。如果对象采用了优化isa引用计数,则判断是都有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合这两种情况中一种的,调用clearDeallocating_slow方法。

  • ⑪最后调用关键的weak_clear_no_lock函数。从表中通过释放对象的地址拿到存放weak指针地址的弱引用表,然后将所有weak指针地址指向的值赋为nil,并从表中删掉该条记录。

总结

weak功能的实现主要就是通过runtime维护一张hash表,在表里存储对象的地址和指向它的所有weak指针的地址。当存储对象被释放时,通过weak_clear_no_lock函数遍历存储weak指针地址的数组,把weak指针置空,从而避免野指针的问题。(因为对象一般分配在堆上的某块内存,如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉;而基础数据类型一般分配在栈上,栈的内存会由系统自己自动处理,不会造成野指针)

原文地址

上一篇:(Python)Gurobi求解多目标指派问题


下一篇:深入理解Objective-C:方法缓存