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

Vla*_*lad 7 swift

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

在方法中loadHappinessV1:

  • 如果self未指定,编译器会引发错误:error:在闭包中引用属性'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 .(")
   }
}
Run Code Online (Sandbox Code Playgroud)

Vla*_*lad 7

我找到了一些解释如何使用嵌套函数捕获语义。来源:嵌套函数和引用捕获

考虑以下示例:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

Compiler 提醒我们在 function 中保持所有权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
}
Run Code Online (Sandbox Code Playgroud)

该行strong_retain %0 : $Test, loc "Test.swift":9:16, scope 3 // id: %3告诉我们编译器对$Test(定义为self)进行强引用,该引用存在于作用域3(即functionA)中,并且在离开作用域时不会释放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
}
Run Code Online (Sandbox Code Playgroud)

因此,如果嵌套函数访问 中定义的某些属性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 .(")
   }
}
Run Code Online (Sandbox Code Playgroud)


Str*_*ers -1

我的第一个猜测是 Swift 隐式地​​将嵌套函数定义为 @noescape 函数或 Autoclosure。(这里有一些信息)。对于这两种类型中的任何一种,您都不必使用“self”,并且 hapynessOp 块将捕获对嵌套函数的引用,因此不会有任何问题

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