为简单的操作避开【弱势自我】?

Avoiding [weak self] for simple operations?

对于短运行操作,是否可以避免[weak self]?例如,URLSession 将保留来自 dataTask(with:completion:):

的闭包
final class ViewController: UIViewController {
  let label = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()
    URLSession.shared.dataTask(with: url) { data, response, error in
      guard let data = data else { return }
      let decodedString = String(bytes: data, encoding: .utf8)

      DispatchQueue.main.async {
        self.label.text = decodedString
      }
    }.resume()
  }
}

在这种情况下,闭包强烈捕获 self,这意味着即使这个 ViewController 被闭包保存在内存中。 URLSession 将保持闭包直到数据任务完成,这意味着 ViewController 的生命周期可能会延长到 dataTask 完成。

在这种情况下,我们是否应该使用捕获列表来避免这种行为?我的推理是否正确,这里没有引用循环?

the life cycle of the ViewController can potentially be extended until dataTask completes

所以问题是这是否连贯。它甚至可能是 good 的东西。如果可以,那很好,不需要 weak self,因为没有保留周期,因为 url 会话已共享。

但是当url会话是一个实例 属性 并且有一个真正的代表,事情要复杂得多 你真的可以获得一个保留周期,因为会话保留了它的委托,而委托可能正在保留会话。

如果您担心引用循环,那么在使用 URL 请求时通常不会得到引用循环。问题是 URL 请求迟早会完成(几分钟后),并且您的控制器会被释放。引用循环只是暂时的,不会造成内存泄漏。

问题是,即使用户已经关闭了控制器并且它永远不会再次显示,您是否希望将控制器保留在内存中。它可能不会引起任何问题,但它仍然是浪费。您正在保留不需要且无法重复使用的内存。

另请注意,您可能实际上想要在关闭控制器时取消 运行 请求,以避免 sending/receiving 不再需要的数据。

在我看来,您不应该太担心引用周期,而应多考虑所有权。强引用意味着拥有某些东西。该请求没有理由 "own" 控制器。恰恰相反——控制器拥有并管理请求。如果没有所有权,为了清楚起见,我会使用 weak

Is my reasoning correct that there's no reference cycle here?

这里没有引用循环。 ViewController 没有保留 dataTask 完成处理程序。您可以将其视为 iOS 保持对视图控制器和完成处理程序的强引用,并且完成处理程序也保持对视图控制器的强引用。没有从视图控制器返回到完成处理程序的强引用,或者到任何引用完成处理程序的对象链,所以你是无循环的。在 UIView.animate 中寻找相同的模式,您再次将闭包发送到 iOS 而不是将它们存储在本地。

For short-running operations, is it acceptable to avoid [weak self]?

工作的持续时间不是一个因素。两个相关的问题是:

  1. 是否存在引用循环?
  2. 引用循环会被打破吗?

举个例子:

class BadVC: UIViewController {
    private lazy var cycleMaker: () -> Void = { print(self) }

    override func loadView() {
        view = UIView()
        cycleMaker()
    }
}

BadVC 这里设法创建了一个引用循环,一旦加载它的视图就永远不会被打破。 cycleMaker() 将在纳秒内执行的事实并不能使我们免于内存泄漏。


实用的,还有第三个问题:

  1. 此代码是否以难以理解、容易破坏或不可靠的方式避免了永久引用循环,因此将来可能会因误用或修改而出现引用循环?

您可以手动中断引用循环。例如:

class StillBadVC: UIViewController {
    private lazy var cycleMaker: () -> Void = { print(self) }

    override func loadView() {
        view = UIView()
        cycleMaker()
    }

    func breakCycle() {
        cycleMaker = { }
    }
}

在这里,我们处于危险之中,因为 StillBadVCcycleMaker 有强引用,而 cycleMaker 捕获对 StillBadVC 的强引用。只要有人记得调用 breakCycle(),这个循环就会被打破,此时视图控制器将删除它对 cycleMaker 的强引用,允许 cycleMaker 释放。但是,如果有人忘记调用 breakCycle(),循环将 而不会 被打破。调用名为 breakCycle() 的方法通常不是使用视图控制器的约定的一部分,因此我们预计 StillBadVC 在实践中会导致内存泄漏。

我想你已经在这里得到了答案。这不是参考循环。

但是要建立一个系统的方法,我在这里的建议要简单得多。忘记考虑泄漏和其他东西。

考虑所有权流量控制然后memory-management

  • 所有权:这个object是否需要拥有另一个object? object 应该拥有它的代表吗?子视图应该拥有它的父母吗?此请求是否拥有此 viewController?
  • 流量控制:我想de-allocate这个object多久?立即或在视图从屏幕上删除后?
  • Memory-management:这是强引用循环吗?

这个思考过程不仅可以帮助您区分真正的内存泄漏和非泄漏。它还可以帮助您更好地设计和阅读您的代码,而不仅仅是盲目地将 [weak self] 到处转储。

你绝对应该在这里使用 [weak self],不是因为强引用循环的任何风险,而是因为这个闭包的存在只是为了更新标签。编写代码故意将视图控制器及其视图保留在内存中是没有意义的,这样您就可以更新可能已被关闭且不再可见的视图中的标签。

weak 关键字的存在不仅仅是为了避免强引用循环,而是为了准确表示对象所有权和管理对象生命周期。您不应该仅仅为了节省与 [weak self] 捕获列表相关的几次击键而歪曲对象所有权图。