其实好多技术我们用的都很多,但是如果展开其中的细节原理,不一定能说的清楚。今天就说一下我们常用的属性修饰词weak
例子
@interface Person : NSObject
@property (nonatomic, strong) Person *friend;
@end@implementation Person
@endint main() {Person *person1 = [[Person alloc] init];Person *person2 = [[Person alloc] init];person1.friend = person2;person2.friend = person1;// 使用 weak 防止循环引用__weak Person *weakPerson = person1;person1 = nil; // 这里 person1 被释放,weakPerson 自动变为 nilNSLog(@"%@", weakPerson); // 输出:nullreturn 0;
}
在这个例子中,person1
和 person2
之间互相引用。如果我们使用 strong
修饰符,它们会互相持有对方的强引用,从而形成循环引用,导致它们无法释放。但是,当我们将 person1
赋值给 weakPerson
后,weakPerson
不增加 person1
的引用计数,因此当 person1
被置为 nil
时,weakPerson
也会自动被置为 nil
。
这背后的机制涉及 Side Table 结构和 objc_storeWeak
、objc_loadWeak
这两个函数的作用。
核心数据结构
weak
机制主要依赖于两个核心的数据结构:SideTable
和 weak_table_t
。
1. SideTable
在 Objective - C 运行时中,存在多个 SideTable
实例,这些实例被组织成一个全局的哈希表。每个 SideTable
结构体包含三个主要成员:
struct SideTable {spinlock_t slock; // 自旋锁,用于保证线程安全RefcountMap refcnts; // 引用计数表,记录对象的引用计数weak_table_t weak_table; // 弱引用表,记录对象的弱引用信息
};
2. weak_table_t
weak_table_t
是 SideTable
中的弱引用表,其结构如下:
struct weak_table_t {weak_entry_t *weak_entries; // 弱引用条目数组size_t num_entries; // 弱引用条目数量uintptr_t mask; // 哈希表的掩码,用于计算哈希索引uintptr_t max_hash_displacement; // 最大哈希冲突位移
};
weak_entries
:是一个指向weak_entry_t
数组的指针,每个weak_entry_t
记录了一个对象的所有弱引用信息。num_entries
:表示当前弱引用条目的数量。mask
:用于计算对象在weak_entries
数组中的哈希索引。max_hash_displacement
:记录了在处理哈希冲突时的最大位移量。
3. weak_entry_t
weak_entry_t
结构体用于存储一个对象的所有弱引用信息,其结构如下:
typedef 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 {// 内联存储,用于存储少量弱引用weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];};};
} weak_entry_t;
referent
:是一个伪装指针,指向被引用的对象。referrers
:是一个指向weak_referrer_t
数组的指针,weak_referrer_t
实际上就是objc_object **
类型,用于存储所有指向该对象的弱引用指针。num_refs
:记录了当前对象的弱引用指针数量。
实现流程
1. 弱引用的创建
当使用 __weak
修饰一个变量并指向一个对象时,会调用 objc_initWeak
函数,该函数的主要流程如下:
- 检查对象是否为
nil
,如果为nil
,则直接将弱引用指针置为nil
。 - 调用
storeWeak
函数,在SideTable
中查找对应的weak_table_t
,并在其中为对象创建一个weak_entry_t
条目,将弱引用指针添加到该条目的referrers
数组中。
2. 弱引用的销毁
当被引用的对象即将被释放时,会调用 objc_destroyWeak
函数,该函数的主要流程如下:
- 调用
storeWeak
函数,在SideTable
中查找对应的weak_table_t
和weak_entry_t
条目。 - 从
weak_entry_t
的referrers
数组中移除该弱引用指针。 - 如果
weak_entry_t
中的弱引用指针数量变为 0,则从weak_table_t
中移除该条目。
3. 对象释放时弱引用的置为 nil
当对象的引用计数变为 0 并即将被释放时,会调用 objc_dealloc
函数,该函数会进一步调用 weak_clear_no_lock
函数,该函数的主要流程如下:
- 在
SideTable
中查找对应的weak_table_t
和weak_entry_t
条目。 - 遍历
weak_entry_t
的referrers
数组,将所有弱引用指针置为nil
。 - 从
weak_table_t
中移除该weak_entry_t
条目。