Weak Reference — 什么是弱引用、语法及在移动开发中的应用

作者: IT Sectr 发布日期: 2026-03-30 阅读时间: 9 分钟

Weak Reference(弱引用)— 是一种不会增加ARC中对象持有计数器的引用。根据 Apple Swift Language Guide, 2026,弱引用使用关键字 weak 声明,并且始终具有可选类型。当对象被释放时,所有指向它的弱引用会自动设置为nil,这可以防止悬空指针,并使弱引用成为打破循环引用的安全机制。

要点

  • Weak Reference — 不影响对象引用计数的引用;对象释放时自动归零
  • 声明 — var前的关键字 weak;类型始终为可选类型 (?)
  • 应用 — 委托、闭包、父子关系,用于打破循环引用
  • 安全性 — 对象释放后自动设置为nil(自动归零弱引用)
  • 与unowned的区别 — weak会归零且安全,unowned不会归零且需要生命周期保证

什么是Weak Reference?

Weak Reference — 是ARC(自动引用计数)中非拥有的对象引用。与增加对象引用计数并保证其生命的strong引用不同,weak引用允许对象即使仍有引用也能被释放。释放后,weak引用会自动设置为nil — 这称为自动归零弱引用

自动归零弱引用是Swift和Objective-C运行时的关键特性。当对象的引用计数达到零且对象被释放时,运行时遍历对该对象的所有弱引用(存储在特殊的弱引用表中)并将它们设置为nil。这保证了通过弱引用无法访问已释放的内存(释放后使用)— 任何读取都将返回nil。

根据Apple WWDC 2012 Session 406,自动归零弱引用消除了与悬空指针相关的一整类崩溃错误,这些错误在手动内存管理(MRR)中很常见。在MRR中,弱引用仅以 __unsafe_unretained 的形式存在 — 它们不会归零,访问已释放的对象会导致EXC_BAD_ACCESS。

Swift和Objective-C中的weak语法

让我们看看Apple生态系统中两种语言的弱引用声明语法。尽管运行时相同,但语法不同,不过语义相同

Swift

在Swift中,弱引用使用关键字 weak 在var之前声明。类型必须始终是可选类型(Type?),因为引用随时可能归零。常量(let)不能是weak — 只能是变量。

swift
class ViewController: UIViewController {
    // weak属性:仅var,仅optional
    weak var delegate: ViewControllerDelegate?
    weak var parentView: UIView?

    weak var completionHandler: ((Bool) -> Void)?  // ⚠️ 闭包不存储weak
    // ⬆️ 错误:weak仅适用于class类型,不适用于closure
}

重要:weak仅适用于类实例(class-types)、AnyObject和继承自AnyObject的协议。Struct、enum和closure不能是weak — 它们是值类型,不参与ARC。

Objective-C

Objective-C中,weak属性通过 __weak 属性或property中的 weak 修饰符声明:

objective-c
// Objective-C: weak属性
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end

// 局部weak变量
__weak MyObject *weakRef = someStrongObject;

Objective-C运行时也提供自动归零弱引用,但额外阻止将weak与C结构和某些Core Foundation对象一起使用。对于它们使用 __unsafe_unretained — 没有自动归零。

何时使用弱引用

弱引用 — 不是通用解决方案,而是特定场景的工具。到处使用weak会导致过度复杂化并降低可读性。让我们看看正确的使用场景。

委托(Delegate模式)

委托 — weak的主要场景。所有者对象(例如UITableView)对自己持有强引用,而委托(UIViewController)不应拥有表格。Apple SDK保证所有delegate和dataSource都是weak的。对于自己的协议,始终使用 weak var delegate

具有反馈的父子关系

当子对象需要引用父对象时(例如ChildViewController访问协调器),使用弱引用。父对象拥有子对象(strong),子对象观察父对象(weak)— 排除循环引用。

异步闭包

捕获列表 [weak self] — 避免存储在类属性中的闭包中循环引用的标准方式。如果self可能在闭包完成前释放 — weak self是必需的。

场景WeakStrong
Delegate✅ 总是weak❌ 循环引用
Parent → Child❌ 不需要(父对象应拥有)✅ Strong
Child → Parent✅ Weak❌ 循环引用
异步回调✅ [weak self]❌ 循环引用风险
强关联(owned)❌ unowned✅ Strong

通用规则:如果对象A拥有B(A → B strong),那么B → A应该是weak或unowned。强引用的方向应始终从所有者到下属。

Weak vs Unowned:比较与场景

weak和unowned都不增加引用计数,但在对象释放后的行为不同。它们之间的选择是生命周期保证的问题。

区别

Weak:自动归零(nil),类型始终为可选类型,使用前需要解包。安全 — 访问nil不会导致崩溃。

Unowned:不会归零,类型为非可选类型。如果对象已被释放,unowned引用变为悬空指针 — 访问它会导致运行时崩溃。Unowned假定对象的生命不短于引用方。

何时选择weak

Weak 选择条件:对象可能随时被释放(屏幕关闭后的委托),你不控制对象的生命周期,或对保证有疑问。Weak — 通用安全选择。

何时选择unowned

Unowned 选择条件:对象保证不会比引用方更早释放(例如Customer → CreditCard,卡不能没有客户)。Uowned提供无需解包的非可选API,在代码中更方便。

swift
class Order {
    let id: Int
    var items: [Item] = []

    init(id: Int) { self.id = id }

    // 强关联:Order拥有Item
    func addItem(name: String) {
        let item = Item(name: name, order: self)
        items.append(item)
    }
}

class Item {
    let name: String
    unowned let order: Order          // ✅ unowned — Item不能没有Order

    init(name: String, order: Order) {
        self.name = name
        self.order = order
    }
}

// weak示例:没有生命周期保证的委托
protocol NetworkServiceDelegate: AnyObject {
    func didReceiveResponse(data: Data)
}

class NetworkService {
    weak var delegate: NetworkServiceDelegate?  // ✅ weak — 委托可以离开
}

在示例中,Item使用unowned,因为订单项不能没有订单本身存在 — 生命周期保证是铁定的。NetworkService使用weak,因为委托(例如ViewController)可能随时关闭和释放。

弱引用的限制与陷阱

弱引用 — 强大的工具,但有限制,理解这些限制对于在iOS开发中正确应用很重要。

Weak的性能

弱引用比强引用慢:每次访问时运行时检查对象是否已释放(在弱引用表中查找)。在绝大多数场景中差异不明显,但在数百万次访问的热循环中,weak可能成为瓶颈。对于高负载场景使用strong并重构架构。

Weak不适用于值类型

Struct, enum, tuple — 不参与ARC的值类型。尝试声明weak struct会导致编译错误。要存储对值类型的弱引用,请使用class类型的包装器或闭包。

多线程中的Weak

自动归零弱引用是线程安全的:如果对象在一个线程上释放,弱引用会在所有线程上原子地归零。然而,读取弱引用与使用之间的间隙可能导致竞态条件 — 对象在获取弱引用和使用它之间被释放。解决方案:强捕获弱引用到局部变量。

swift
// 多线程中weak的竞态条件
func performAsync() {
    weak var weakSelf = self
    queue.async {
        // ⚠️ weakSelf在检查和使用之间可能为nil
        if weakSelf != nil {
            weakSelf!.doSomething()  // 如果变为nil则崩溃
        }
    }
}

// ✅ 修复:在使用期间强捕获
func performAsyncSafe() {
    queue.async { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.doSomething()  // strongSelf — 局部强引用
    }
}

在安全变体中,weak self被捕获,然后立即解包到局部强变量 strongSelf。如果self仍然存活 — 它将在块执行期间保持存活。如果没有 — guard被触发,代码不会执行。这个习惯用法是Swift中异步闭包的标准模式。

UIView和weak outlet

IBOutlet在Interface Builder中应该是weak的,因为视图层次结构已经持有对subview的强引用。在控制器中重复强引用不会创建循环引用,但多余。对outlet的弱引用 — Apple的建议,尽管许多开发人员为了简化代码而使用strong。

常见问题

弱引用可以指向尚未创建的对象吗?

不能,weak只能指向现有对象或nil。创建新对象时,首先获得强引用(通过初始化器),然后才能分配弱引用。初始时的weak nil — 正常状态。

为什么weak只对类类型有效?

Weak基于ARC,ARC只管理引用类型(类)。值类型(struct、enum)在赋值时复制,没有引用计数。对于值类型的弱关联,使用闭包或带有weak属性的class包装器。

Weak在循环中如何影响性能?

每次访问弱引用时在运行时表中执行查找。在具有数百万次迭代的循环中,这可能比强引用慢2–5倍。对于热路径,在循环之前将weak复制到局部强变量。

弱引用何时会意外变为nil?

当对象的所有强引用丢失时 — 在作用域结束时、重新设置属性时、关闭屏幕时。在多线程环境中,这可能在两行代码之间发生。始终通过guard let或if let检查weak。

Weak与Objective-C中的__weak有什么不同?

语义相同:两者都提供自动归零弱引用。区别:Swift需要可选类型和var,Objective-C使用property修饰符。Objective-C还支持 __unsafe_unretained — 无自动归零的弱引用(悬空指针风险)。

总结

  • Weak Reference — 非拥有引用,不增加引用计数,释放时自动归零
  • 语法weak var + 可选类型;仅类类型和AnyObject协议
  • 自动归零弱引用 — 运行时将所有对已释放对象的弱引用归零,防止悬空指针
  • 场景 — 委托、具有反馈的父子关系、异步闭包([weak self])
  • Weak vs Unowned — weak归零(安全),unowned不归零(崩溃风险,但非可选)
  • 性能 — weak因运行时表查找比strong慢;热路径复制到strong
  • 建议 — 如果不确定生命周期保证 — 选择weak

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读