为什么捕获列表中的捕获说明符是可选的?

Why is the capture specifier optional in capture lists?

Swift 中的捕获列表语法中似乎存在一个奇怪的语法错误。如果我声明了多个捕获变量,则捕获说明符仅适用于第一个变量:

let closure = { [unowned x, y] in … }

现在我希望 yunowned,但似乎并非如此:

class Test {

    var callback: (Void -> Void)?

    init() {
        print("init")
    }

    deinit {
        print("deinit")
    }
}

func makeScope() {
    let x = Test()
    let y = Test()
    y.callback = { [unowned x, y] in
        print(y)
    }
}

makeScope()
print("done")

这会打印:

init
init
deinit
done

所以y似乎被强捕获并创建了一个保留循环,阻止了对象被释放。是这样吗?如果是,在列表中允许“空”捕获说明符是否有意义?或者有什么原因 [unowned x, y] 不被视为 [unowned x, unowned y]

根据 syntax EBNFunowned 捕获说明符 的这种处理是完全有意的:

closure-signature → parameter-clause­ function-result­opt in­
closure-signature → identifier-list­ function-result­opt ­in­
closure-signature → capture-list­ parameter-clause ­function-resultopt in­
closure-signature → capture-list ­identifier-list ­function-result­opt ­in
­ closure-signature → capture-list­ in­
capture-list → [­capture-list-items­]­
capture-list-items → capture-list-item­ capture-list-item ­capture-list-items
­ capture-list-item → capture-specifieropt ­expression­
capture-specifier → weak­ | unowned­ | unowned(safe)­ | unowned(unsafe)­

定义 <capture-list-items><capture-list-item><capture-specifier> 产品的底部三行在这里最相关。

<capture-list-items> 产生式是 <capture-list-item> 的逗号分隔列表,capture-specifier 附加到每个 <capture-list-item>,而不是 <capture-list-items>整体列出。

这很有意义,因为它使程序员可以完全控制对单个参数的捕获。当说明符应用于整个列表时的替代方案将取消这种灵活性。

why would one want to include an identifier in the capture list without modifying its capture specifier?

看来 Swift 的设计者的理念是尽可能提供智能默认行为。在大多数情况下,Swift 可以根据表达式的类型找到一种捕获最有意义的表达式的方法,而无需程序员参与。当编译器没有足够的信息来找出基于上下文捕获变量的正确方法时,显式捕获说明符用于特殊情况。

... does it make sense to permit an “empty” capture specifier in the list?

是的。 捕获说明符("weak"、"unowned" 及其变体)只能与 引用类型 一起使用,但也有一些情况需要捕获 值类型 (这里是一个例子: ).

您可能还想强捕获引用类型。 捕获引用类型确保引用(指针) 本身是按值捕获的,如以下示例所示:

class MyClass {
    let value : String
    init(value : String) {
        self.value = value
    }
}

var ref = MyClass(value: "A")

let clo1: () -> Void = { print(ref.value) }
let clo2: () -> Void = { [ref] in print(ref.value) }

ref = MyClass(value: "B")

clo1() // Output: B
clo2() // Output: A

当第一个闭包执行时,ref闭包内部 是对创建为 MyClass(value: "B").

的对象的引用

第二个闭包捕获 ref 的值 关闭被创建,并且当一个新值时这不会改变 分配给 var ref.

回答您的具体问题:

Why is the capture specifier optional in capture lists?

因为默认行为是捕获任何必要的变量(强烈捕获引用类型)。默认情况下,如果要使用它们的值,则无需在捕获列表中明确指定它们。 (尽管如果您要捕获 self,则需要使用 self.property 进行资格赛。)

…is there a reason [unowned x, y] is not treated as [unowned x, unowned y]?

同理:默认是强捕获。 unowned 不适用于捕获列表中的其他项目;这不是现在语法的工作方式。