微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

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

解决方法

我找到了一些解释如何使用嵌套函数捕获语义.资料来源: 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的所有权.但是没有说明在功能函数A中捕获自我的任何信息.

让我们看看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,scope 3 // id: %3
  %4 = partial_apply %2(%0) : $@convention(thin) (@owned Test) -> (),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,范围3 // id:%3告诉我们编译器为$Test(定义为self)做强引用,此引用存在于范围3(即功能A)并且在离开范围3时未释放.

第二个函数closureA处理self的可选引用.它在代码中表示为%2 = alloc_Box $@ sil_weak可选< Test>,var,名称“self”,loc“Test.swift”:13:38,范围8 //用户:?,?,%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,loc "Test.swift":12:10,scope 6 // id: %1
  %2 = alloc_Box $@sil_weak Optional<Test>,loc "Test.swift":13:38,scope 8 // users: %13,%11,%9,%3
  %3 = project_Box %2 : $@Box @sil_weak Optional<Test>,scope 8 // users: %10,%6
  strong_retain %0 : $Test,scope 8 // id: %4
  %5 = enum $Optional<Test>,#Optional.some!enumelt.1,%0 : $Test,scope 8 // users: %7,%6
  store_weak %5 to [initialization] %3 : $*@sil_weak Optional<Test>,scope 8 // id: %6
  release_value %5 : $Optional<Test>,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>,scope 8 // id: %9
  mark_function_escape %3 : $*@sil_weak Optional<Test>,scope 8 // id: %10
  %11 = partial_apply %8(%2) : $@convention(thin) (@owned @Box @sil_weak Optional<Test>) -> (),scope 8 // users: %14,%12
  debug_value %11 : $@callee_owned () -> (),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).

为了避免这种行为,我们只需要使用闭包而不是嵌套函数.然后编译器将通知自我使用情况.

原始示例可以重新表示如下:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐