【问题标题】:Avoiding [weak self] for simple operations?避免[弱自我]进行简单操作?
【发布时间】:2020-06-01 11:51:27
【问题描述】:

对于短期运行的操作,是否可以避免使用[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 完成。

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

【问题讨论】:

    标签: ios swift memory-management urlsession


    【解决方案1】:

    ViewController 的生命周期可能会延长,直到 dataTask 完成

    所以问题是这是否是连贯的。这甚至可能是一件的事情。如果可以,那很好,并且不需要weak self,因为没有保留周期,因为 url 会话是共享的。

    但是当 url session 是一个实例时 财产并且有一个真正的代表,事情要复杂得多, 你真的可以得到一个保留周期,因为会话保留了它的代表,它可能会保留会话。

    【讨论】:

      【解决方案2】:

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

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

      另请注意,您实际上可能希望在控制器被解除时取消正在运行的请求,以避免发送/接收不再需要的数据。

      在我看来,您不应该过多地担心参考周期,而应该多考虑所有权。强引用意味着拥有某物。该请求没有理由“拥有”控制器。反过来——控制器拥有并管理请求。如果没有所有权,我会使用weak 来说明清楚。

      【讨论】:

        【解决方案3】:

        我的推理是否正确,这里没有引用循环?

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

        对于短期操作,是否可以避免[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 = { }
            }
        }
        

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

        【讨论】:

          【解决方案4】:

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

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

          【讨论】:

            【解决方案5】:

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

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

            考虑所有权流控制,然后考虑内存管理

            • 所有权:这个对象是否需要拥有另一个对象?一个对象应该拥有它的委托吗?子视图应该拥有它的父视图吗?此请求是否拥有此 viewController?
            • 流控制:多久我想解除分配这个对象?立即或在视图从屏幕上移除时?
            • 内存管理:这是一个强大的参考循环吗?

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-05-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-13
              • 1970-01-01
              相关资源
              最近更新 更多