1. 什么情况使用 weak 关键字,相比 assign 有什么不同?

11 阅读2分钟

借助AI辅助。

1. 核心区别总结

weak 和 assign 的核心区别在于:当对象被释放(dealloc)后,指针会发生什么变化。

  • weak :会自动被置为 nil (即 Zeroing Weak Reference )。向 nil 发送消息是安全的,不会崩溃。
  • assign :指针保持不变,仍然指向原来的内存地址,变成 野指针(Dangling Pointer) 。如果后续尝试访问该内存,会导致 EXC_BAD_ACCESS 崩溃。

2. 具体使用场景

什么时候用 weak ?

主要用于 解决循环引用(Retain Cycle) 问题,同时确保访问安全。

  1. Delegate(代理模式) :delegate 属性通常设为 weak,比如UITableView的delegate属性。
  2. Block 内部捕获外部变量 :在 Block 内部使用 self 时,通常需要 __weak typeof(self) weakSelf = self; 来打破闭环。
  3. UI 控件(IBOutlet) :虽然现代 iOS 开发中强引用(strong)IBOutlet 也没问题,但对于视图层级中已经存在的子视图,使用 weak 是一种“我不拥有你,我只是指向你”的语义表达。

什么时候用 assign ?

  1. 基本数据类型 :如 int , float , BOOL , NSInteger , CGFloat , struct 等。因为这些类型不是对象,分配在栈上,没有引用计数的概念,不需要内存管理。
  2. (不推荐) MRC 时代的遗留代码 :在 ARC 之前,为了避免循环引用可能会用到 assign 修饰对象(类似 unsafe_unretained ),但在 ARC 下严禁对对象使用 assign ,除非你极其确定对象的生命周期(极易引发 Crash)。

3. 进阶追问

“你说 weak 会自动置为 nil ,那 Runtime 是 如何实现 这一点的?”

回答思路:

  1. SideTable 结构 : Runtime 维护了一个全局的 Hash 表,叫做 SideTables 。里面存储了多个 SideTable 结构体(使用分离锁 Striped Locks 来提高并发效率)。

  2. Weak 表(weak_table_t) : 每个 SideTable 中包含一个 weak_table 。这是一个 Hash 表,其 Key 是对象的内存地址, Value 是一个数组( weak_entry_t ),数组里存着所有指向该对象的 weak 指针的地址。

  3. 置 nil 的过程 :

    • 当一个对象调用 dealloc 方法时,Runtime 最终会调用 weak_clear_no_lock 函数。
    • 该函数根据对象的地址,在 weak_table 中查找对应的条目。
    • 一旦找到,它会遍历 Value 数组(即所有指向该对象的 weak 指针),将这些指针所指向的内存地址全部置为 nil 。
    • 最后把该记录从 weak_table 中移除。

参考文档

  1. 揭开 iOS 中 weak 指针的神秘面纱:从原理到实践