直接在闭包中捕获值时的内存语义

时间:2018-05-22 11:25:24

标签: swift

我最近在objc.io的这篇文章中读到了有关捕获列表的内容。我认为这是一个很好的提示,我开始使用它。

虽然没有完全澄清,但我认为在捕获这种方式时没有保留周期,因此您可以获得捕获的强引用,但不会担心保留周期。

我意识到甚至可以捕获方法,而不仅仅是值:

.subscribe(onNext: { [showErrorAlert, populate] result in
    switch result {
    case .success(let book):
        populate(book)
    case .error(_):
        showErrorAlert(L10n.errorExecutingOperation.localized)
    }
})

我试图找到一些与这种捕获方式相关的文档,但我找不到任何文档。这种做法安全吗?这是否等于封闭内[weak self]strongSelf = self的常规舞蹈?

2 个答案:

答案 0 :(得分:1)

  

这种做法安全吗?这是否相当于[弱者]的平常舞蹈   自我],strongSelf =自我封闭内部?

是和否 - 捕获对象的方法也会保留对象。捕获的方法可以从实例访问任何内容,因此保留它是有道理的。

另一方面,捕获属性不会保留实例。

这是一个简短的片段,您可以在操场上粘贴以便自己查看:

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class A {
    let name: String
    init(name: String) {
        self.name = name
    }

    func a() {
        print("Hello from \(name)")
    }

    func scheduleCaptured() {
        print("Scheduling captured method")
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [a] in
            a()
        }
    }

    func scheduleCapturedVariable() {
        print("Scheduling captured variable")
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { [name] in
            print("Hello from \(name)")
        }
    }

    deinit {
        print("\(name) deinit")
    }
}

var a1: A? = A(name: "instance 1")
a1?.scheduleCapturedVariable()

var a2: A? = A(name: "instance 2")
a2?.scheduleCaptured()

a1 = nil
a2 = nil

输出结果为:

Scheduling captured variable
Scheduling captured method
instance 1 deinit
Hello from instance 1
Hello from instance 2
instance 2 deinit

您可以看到instance 2在被捕获的块被触发之前未被取消初始化,而instance 1在设置为nil后立即被取消初始化。

答案 1 :(得分:1)

  1. 捕捉实例方法是不安全的,除非你确定你的封闭将在deinit之前被处理掉(你绝对确定它会触发有限的次数和然后序列总是结束)。对于非反应性用例也是如此。
  2. 该方法被强烈捕获(并且无法被弱捕获),因此闭包将保留参考,禁止ARC破坏它。因此,使用strongSelf行为闭包只保留对对象的弱引用,在执行开始时将其绑定为强,然后在执行结束时释放强引用。