중첩 된 함수로 작업하는 캡처 의미 체계가 어떤 설명을 발견했습니다. 출처 : 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
의 소유권을 유지하기 위해 우리를 생각 나게한다. 그러나 기능 functionA
에서 self
을 캡처하는 것에 대해서는 아무것도 말하지 않습니다.
는 스위프트 중간 언어에 보면 (
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
}
라인 strong_retain %0 : $Test, loc "Test.swift":9:16, scope 3 // id: %3
우리에게 (self
로 정의된다) $Test
에 대한 강한 참조를 만들고 그 컴파일러, 범위 3
에서이 참조의 삶을 알려줍니다 (functionA
)이고 범위를 벗어날 때 해제되지 않습니다 3
.
보조 기능 closureA
은 self
에 대한 참조를 참조합니다. 코드에서 %2 = alloc_box [email protected]_weak Optional<Test>, var, name "self", loc "Test.swift":13:38, scope 8 // users: %13, %11, %9, %3
으로 표시됩니다. 중첩 함수 self
정의 일부 속성에 액세스하면
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
}
그래서, 만약 중첩 함수 self
강한 참조를 유지한다. 컴파일러는 그것에 대해 알리지 않습니다 (Swift 3.0.1).
이 동작을 방지하려면 중첩 된 함수 대신 클로저를 사용해야합니다. 그렇다면 컴파일러는 self
사용에 대해 통지합니다.
원래 예는 다음과 같은 rewtitten 수 : 나는 모두 당신의 기능 자체의 맥락에서 존재하지 않고 즉 있다는 것입니다 이유는하지만 난 알아낼 수 무엇인지에 완전히 확실하지 않다
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 .(")
}
}
왜 컴파일러가 V1에서와 같이 자신의 컨텍스트에 존재하는 메서드를 호출하려고하는지 불평하지 않습니다. – Harsh