Tôi đã tìm thấy một số giải thích cách ngữ nghĩa nắm bắt hoạt động với các hàm lồng nhau. Nguồn: Nested functions and reference capturing.
xem xét sau Ví dụ:
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
}
}
Compiler nhắc nhở chúng ta phải duy trì quyền sở hữu chức năng closureA
. Nhưng không nói gì về việc chụp self
trong hàm functionA
.
Hãy nhìn Swift Intermediate Language (SIL):
xcrun swiftc -emit-silgen Test.swift | xcrun swift-demangle > Test.silgen
sil_scope 2 { loc "Test.swift":5:10 parent @Test.Test.functionA() ->() ->() : [email protected](method) (@guaranteed Test) -> @owned @callee_owned() ->() }
sil_scope 3 { loc "Test.swift":10:5 parent 2 }
// Test.functionA() ->() ->()
sil hidden @Test.Test.functionA() ->() ->() : [email protected](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)() ->() : [email protected](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) : [email protected](thin) (@owned Test) ->(), loc "Test.swift":9:16, scope 3 // user: %5
return %4 : [email protected]_owned() ->(), loc "Test.swift":9:9, scope 3 // id: %5
}
Dòng strong_retain %0 : $Test, loc "Test.swift":9:16, scope 3 // id: %3
cho chúng ta biết rằng trình biên dịch làm tài liệu tham khảo mạnh mẽ cho $Test
(được định nghĩa là self
), tài liệu tham khảo này cuộc sống trong phạm vi 3
(là functionA
) và không được phát hành tại thời điểm rời khỏi phạm vi 3
.
Chức năng thứ hai closureA
giao dịch với tham chiếu tùy chọn đến self
. Nó được thể hiện bằng mã số là %2 = alloc_box [email protected]_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 : [email protected](method) (Builtin.Int2048, @thin Int.Type) -> Int
sil_scope 6 { loc "Test.swift":12:10 parent @Test.Test.closureA() ->() ->() : [email protected](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() ->() ->() : [email protected](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 [email protected]_weak Optional<Test>, var, name "self", loc "Test.swift":13:38, scope 8 // users: %13, %11, %9, %3
%3 = project_box %2 : [email protected] @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) : [email protected](thin) (@owned @box @sil_weak Optional<Test>) ->(), loc "Test.swift":13:30, scope 8 // user: %11
strong_retain %2 : [email protected] @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) : [email protected](thin) (@owned @box @sil_weak Optional<Test>) ->(), loc "Test.swift":13:30, scope 8 // users: %14, %12
debug_value %11 : [email protected]_owned() ->(), let, name "nestedClosureA", loc "Test.swift":13:13, scope 7 // id: %12
strong_release %2 : [email protected] @sil_weak Optional<Test>, loc "Test.swift":15:9, scope 7 // id: %13
return %11 : [email protected]_owned() ->(), loc "Test.swift":16:9, scope 7 // id: %14
}
Vì vậy, nếu chức năng lồng truy cập một số tính chất quy định tại self
, sau đó chức năng lồng nhau giữ tham chiếu mạnh để self
. Trình biên dịch không thông báo về nó (Swift 3.0.1).
Để tránh hành vi này, chúng tôi chỉ cần sử dụng các bao đóng thay cho các chức năng lồng nhau. Sau đó trình biên dịch sẽ thông báo về việc sử dụng self
.
gốc ví dụ có thể được rewtitten như sau:
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 .(")
}
}
Tôi không hoàn toàn chắc chắn về những gì lý do là nhưng tôi có thể hình dung ra là cả hai chức năng của bạn không tồn tại trong bối cảnh của bản thân và đó là tại sao trình biên dịch không phàn nàn ở đâu trong V1 bạn đang cố gắng gọi một phương thức tồn tại trong ngữ cảnh của bản thân. – Harsh