2016-08-11 3 views
5

중첩 된 함수가 클로저에서 호출 될 때 작동하는 빠른 캡처 의미를 이해하는 데 도움이 필요합니다. 따라서 두 가지 방법이 있습니다. loadHappinessV1loadHappinessV2입니다. self가 지정되지 않은 경우Swift : 클로저에서 중첩 된 함수를 호출 할 때 의미를 캡처합니다. 컴파일러가 오류를 발생시키지 않는 이유는 무엇입니까?

  • 컴파일러는 오류를 발생 : : 방법 loadHappinessV1에서

    오류 : 폐쇄 재산을 참조하여 'callbackQueue은'명시 적 '. 자신을'필요 캡처 의미를 명시 적으로 만들려면

  • 컴파일러 오류를 방지하기 위해 약한 참조를 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 .(") 
    } 
} 
+1

왜 컴파일러가 V1에서와 같이 자신의 컨텍스트에 존재하는 메서드를 호출하려고하는지 불평하지 않습니다. – Harsh

답변

-1

Swift는 내포 된 함수를 암시 적으로 @noescape 함수 또는 Autoclosure로 정의합니다. (some info here). 이러한 유형 중 하나를 사용하면 "자체"를 사용할 필요가 없으며 hapynessOp 블록은 중첩 된 함수에 대한 참조를 캡처하므로 아무런 문제가 발생하지 않습니다.

그렇지 않으면 중첩 된 함수는 실제로 클래스의 서명에 추가됩니다. 일부 테스트를 수행하고 알아낼 수 있다고 생각하고 있습니다.

2

중첩 된 함수로 작업하는 캡처 의미 체계가 어떤 설명을 발견했습니다. 출처 : 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.

보조 기능 closureAself에 대한 참조를 참조합니다. 코드에서 %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 .(") 
    } 
} 
관련 문제