Swift:从闭包调用嵌套函数时捕获语义。为什么编译器不会引发错误?

时间:2016-08-11 20:59:42

标签: swift

需要你的帮助来理解当从闭包中调用嵌套函数时Swift捕获语义是如何工作的。所以,我有两种方法loadHappinessV1loadHappinessV2

方法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 .(")
   }
}

2 个答案:

答案 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)强引用,此引用位于范围3functionA 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块将捕获对嵌套函数的引用,因此不存在任何问题

否则,嵌套函数实际上可能会添加到类的签名中。我认为可以做一些测试并找出答案(可以解决这个问题)。