需要你的帮助来理解当从闭包中调用嵌套函数时Swift捕获语义是如何工作的。所以,我有两种方法loadHappinessV1
和loadHappinessV2
。
方法loadHappinessV1
:
self
时引发错误: 错误:在闭包中对属性'callbackQueue'的引用需要显式'self'。使捕获语义显式 self
的弱引用。方法loadHappinessV2
:
为什么方法loadHappinessV2
编译器不会引发有关捕获语义的错误?是否未捕获嵌套函数(以及变量callbackQueue
)?
谢谢!
import PlaygroundSupport
import Cocoa
PlaygroundPage.current.needsIndefiniteExecution = true
struct Happiness {
final class Net {
enum LoadResult {
case success
case failure
}
private var callbackQueue: DispatchQueue
private lazy var operationQueue = OperationQueue()
init(callbackQueue: DispatchQueue) {
self.callbackQueue = callbackQueue
}
func loadHappinessV1(completion: (LoadResult) -> Void) {
operationQueue.cancelAllOperations()
let hapynessOp = BlockOperation { [weak self] in
let hapynessGeneratorValue = arc4random_uniform(10)
if hapynessGeneratorValue % 2 == 0 {
// callbackQueue.async { completion(.success) } // Compile error
self?.callbackQueue.async { completion(.success) }
} else {
// callbackQueue.async { completion(.failure) } // Compile error
self?.callbackQueue.async { completion(.failure) }
}
}
operationQueue.addOperation(hapynessOp)
}
func loadHappinessV2(completion: (LoadResult) -> Void) {
operationQueue.cancelAllOperations()
func completeWithFailure() {
callbackQueue.async { completion(.failure) }
}
func completeWithSuccess() {
callbackQueue.async { completion(.success) }
}
let hapynessOp = BlockOperation {
let hapynessGeneratorValue = arc4random_uniform(10)
if hapynessGeneratorValue % 2 == 0 {
completeWithSuccess()
} else {
completeWithFailure()
}
}
operationQueue.addOperation(hapynessOp)
}
}
}
// Usage
let happinessNetV1 = Happiness.Net(callbackQueue: DispatchQueue.main)
happinessNetV1.loadHappinessV1 {
switch $0 {
case .success: print("Happiness V1 delivered .)")
case .failure: print("Happiness V1 not available at the moment .(")
}
}
let happinessNetV2 = Happiness.Net(callbackQueue: DispatchQueue.main)
happinessNetV2.loadHappinessV2 {
switch $0 {
case .success: print("Happiness V2 delivered .)")
case .failure: print("Happiness V2 not available at the moment .(")
}
}
答案 0 :(得分:3)
我找到了一些解释如何使用嵌套函数捕获语义。来源:Nested functions and reference capturing。
请考虑以下示例:
class Test {
var bar: Int = 0
func functionA() -> (() -> ()) {
func nestedA() {
bar += 1
}
return nestedA
}
func closureA() -> (() -> ()) {
let nestedClosureA = { [unowned self] () -> () in
self.bar += 1
}
return nestedClosureA
}
}
编译器提醒我们保持函数closureA
的所有权。但是没有说明在函数self
中捕获functionA
。
让我们看一下Swift中间语言(SIL):
xcrun swiftc -emit-silgen Test.swift | xcrun swift-demangle > Test.silgen
sil_scope 2 { loc "Test.swift":5:10 parent @Test.Test.functionA () -> () -> () : $@convention(method) (@guaranteed Test) -> @owned @callee_owned () -> () }
sil_scope 3 { loc "Test.swift":10:5 parent 2 }
// Test.functionA() -> () -> ()
sil hidden @Test.Test.functionA () -> () -> () : $@convention(method) (@guaranteed Test) -> @owned @callee_owned () -> () {
// %0 // users: %4, %3, %1
bb0(%0 : $Test):
debug_value %0 : $Test, let, name "self", argno 1, loc "Test.swift":5:10, scope 2 // id: %1
// function_ref Test.(functionA() -> () -> ()).(nestedA #1)() -> ()
%2 = function_ref @Test.Test.(functionA () -> () -> ()).(nestedA #1) () -> () : $@convention(thin) (@owned Test) -> (), loc "Test.swift":9:16, scope 3 // user: %4
strong_retain %0 : $Test, loc "Test.swift":9:16, scope 3 // id: %3
%4 = partial_apply %2(%0) : $@convention(thin) (@owned Test) -> (), loc "Test.swift":9:16, scope 3 // user: %5
return %4 : $@callee_owned () -> (), loc "Test.swift":9:9, scope 3 // id: %5
}
第strong_retain %0 : $Test, loc "Test.swift":9:16, scope 3 // id: %3
行告诉我们编译器为$Test
(定义为self
)强引用,此引用位于范围3
(functionA
1}})并且在离开范围3
时未发布。
第二个函数closureA
处理self
的可选引用。它在代码中表示为%2 = alloc_box $@sil_weak Optional<Test>, var, name "self", loc "Test.swift":13:38, scope 8 // users: %13, %11, %9, %3
。
sil [transparent] [fragile] @Swift.Int.init (_builtinIntegerLiteral : Builtin.Int2048) -> Swift.Int : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int
sil_scope 6 { loc "Test.swift":12:10 parent @Test.Test.closureA () -> () -> () : $@convention(method) (@guaranteed Test) -> @owned @callee_owned () -> () }
sil_scope 7 { loc "Test.swift":17:5 parent 6 }
sil_scope 8 { loc "Test.swift":15:9 parent 7 }
// Test.closureA() -> () -> ()
sil hidden @Test.Test.closureA () -> () -> () : $@convention(method) (@guaranteed Test) -> @owned @callee_owned () -> () {
// %0 // users: %5, %4, %1
bb0(%0 : $Test):
debug_value %0 : $Test, let, name "self", argno 1, loc "Test.swift":12:10, scope 6 // id: %1
%2 = alloc_box $@sil_weak Optional<Test>, var, name "self", loc "Test.swift":13:38, scope 8 // users: %13, %11, %9, %3
%3 = project_box %2 : $@box @sil_weak Optional<Test>, loc "Test.swift":13:38, scope 8 // users: %10, %6
strong_retain %0 : $Test, loc "Test.swift":13:38, scope 8 // id: %4
%5 = enum $Optional<Test>, #Optional.some!enumelt.1, %0 : $Test, loc "Test.swift":13:38, scope 8 // users: %7, %6
store_weak %5 to [initialization] %3 : $*@sil_weak Optional<Test>, loc "Test.swift":13:38, scope 8 // id: %6
release_value %5 : $Optional<Test>, loc "Test.swift":13:38, scope 8 // id: %7
// function_ref Test.(closureA() -> () -> ()).(closure #1)
%8 = function_ref @Test.Test.(closureA () -> () -> ()).(closure #1) : $@convention(thin) (@owned @box @sil_weak Optional<Test>) -> (), loc "Test.swift":13:30, scope 8 // user: %11
strong_retain %2 : $@box @sil_weak Optional<Test>, loc "Test.swift":13:30, scope 8 // id: %9
mark_function_escape %3 : $*@sil_weak Optional<Test>, loc "Test.swift":13:30, scope 8 // id: %10
%11 = partial_apply %8(%2) : $@convention(thin) (@owned @box @sil_weak Optional<Test>) -> (), loc "Test.swift":13:30, scope 8 // users: %14, %12
debug_value %11 : $@callee_owned () -> (), let, name "nestedClosureA", loc "Test.swift":13:13, scope 7 // id: %12
strong_release %2 : $@box @sil_weak Optional<Test>, loc "Test.swift":15:9, scope 7 // id: %13
return %11 : $@callee_owned () -> (), loc "Test.swift":16:9, scope 7 // id: %14
}
因此,如果嵌套函数访问self
中定义的某些属性,则嵌套函数会保留对self
的强引用。编译器不会通知它(Swift 3.0.1)。
为了避免这种行为,我们只需要使用闭包而不是嵌套函数。然后编译器将通知self
用法。
原始示例可以重新改写如下:
import PlaygroundSupport
import Cocoa
PlaygroundPage.current.needsIndefiniteExecution = true
struct Happiness {
final class Net {
enum LoadResult {
case success
case failure
}
private var callbackQueue: DispatchQueue
private lazy var operationQueue = OperationQueue()
init(callbackQueue: DispatchQueue) {
self.callbackQueue = callbackQueue
}
func loadHappinessV1(completion: @escaping (LoadResult) -> Void) {
operationQueue.cancelAllOperations()
let hapynessOp = BlockOperation { [weak self] in
let hapynessGeneratorValue = arc4random_uniform(10)
if hapynessGeneratorValue % 2 == 0 {
// callbackQueue.async { completion(.success) } // Compile error
self?.callbackQueue.async { completion(.success) }
} else {
// callbackQueue.async { completion(.failure) } // Compile error
self?.callbackQueue.async { completion(.failure) }
}
}
operationQueue.addOperation(hapynessOp)
}
func loadHappinessV2(completion: @escaping (LoadResult) -> Void) {
operationQueue.cancelAllOperations()
// Closure used instead of nested function.
let completeWithFailure = { [weak self] in
self?.callbackQueue.async { completion(.failure) }
}
// Closure used instead of nested function.
let completeWithSuccess = { [weak self] in
self?.callbackQueue.async { completion(.success) }
}
let hapynessOp = BlockOperation {
let hapynessGeneratorValue = arc4random_uniform(10)
if hapynessGeneratorValue % 2 == 0 {
completeWithSuccess()
} else {
completeWithFailure()
}
}
operationQueue.addOperation(hapynessOp)
}
}
}
// Usage
let happinessNetV1 = Happiness.Net(callbackQueue: DispatchQueue.main)
happinessNetV1.loadHappinessV1 {
switch $0 {
case .success: print("Happiness V1 delivered .)")
case .failure: print("Happiness V1 not available at the moment .(")
}
}
let happinessNetV2 = Happiness.Net(callbackQueue: DispatchQueue.main)
happinessNetV2.loadHappinessV2 {
switch $0 {
case .success: print("Happiness V2 delivered .)")
case .failure: print("Happiness V2 not available at the moment .(")
}
}
答案 1 :(得分:-1)
我的第一个猜测是Swift将嵌套函数隐式定义为@noescape函数或Autoclosure。 (some info here)。对于这些类型中的任何一种,您不必使用“self”,并且hapynessOp块将捕获对嵌套函数的引用,因此不存在任何问题
否则,嵌套函数实际上可能会添加到类的签名中。我认为可以做一些测试并找出答案(可以解决这个问题)。