借助AI辅助。
1. 核心区别总结
weak 和 assign 的核心区别在于:当对象被释放(dealloc)后,指针会发生什么变化。
- weak :会自动被置为 nil (即 Zeroing Weak Reference )。向 nil 发送消息是安全的,不会崩溃。
- assign :指针保持不变,仍然指向原来的内存地址,变成 野指针(Dangling Pointer) 。如果后续尝试访问该内存,会导致 EXC_BAD_ACCESS 崩溃。
2. 具体使用场景
什么时候用 weak ?
主要用于 解决循环引用(Retain Cycle) 问题,同时确保访问安全。
- Delegate(代理模式) :delegate 属性通常设为 weak,比如UITableView的delegate属性。
- Block 内部捕获外部变量 :在 Block 内部使用 self 时,通常需要 __weak typeof(self) weakSelf = self; 来打破闭环。
- UI 控件(IBOutlet) :虽然现代 iOS 开发中强引用(strong)IBOutlet 也没问题,但对于视图层级中已经存在的子视图,使用 weak 是一种“我不拥有你,我只是指向你”的语义表达。
什么时候用 assign ?
- 基本数据类型 :如 int , float , BOOL , NSInteger , CGFloat , struct 等。因为这些类型不是对象,分配在栈上,没有引用计数的概念,不需要内存管理。
- (不推荐) MRC 时代的遗留代码 :在 ARC 之前,为了避免循环引用可能会用到 assign 修饰对象(类似 unsafe_unretained ),但在 ARC 下严禁对对象使用 assign ,除非你极其确定对象的生命周期(极易引发 Crash)。
3. 进阶追问
“你说 weak 会自动置为 nil ,那 Runtime 是 如何实现 这一点的?”
回答思路:
-
SideTable 结构 : Runtime 维护了一个全局的 Hash 表,叫做 SideTables 。里面存储了多个 SideTable 结构体(使用分离锁 Striped Locks 来提高并发效率)。
-
Weak 表(weak_table_t) : 每个 SideTable 中包含一个 weak_table 。这是一个 Hash 表,其 Key 是对象的内存地址, Value 是一个数组( weak_entry_t ),数组里存着所有指向该对象的 weak 指针的地址。
-
置 nil 的过程 :
- 当一个对象调用 dealloc 方法时,Runtime 最终会调用 weak_clear_no_lock 函数。
- 该函数根据对象的地址,在 weak_table 中查找对应的条目。
- 一旦找到,它会遍历 Value 数组(即所有指向该对象的 weak 指针),将这些指针所指向的内存地址全部置为 nil 。
- 最后把该记录从 weak_table 中移除。