【问题标题】:How do I run Asynchronous callbacks in Playground如何在 Playground 中运行异步回调
【发布时间】:2014-07-26 08:05:21
【问题描述】:

许多 Cocoa 和 CocoaTouch 方法都有完成回调,在 Objective-C 中实现为块,在 Swift 中实现为闭包。但是,当在 Playground 中尝试这些时,永远不会调用完成。例如:

// Playground - noun: a place where people can play

import Cocoa
import XCPlayground

let url = NSURL(string: "http://stackoverflow.com")
let request = NSURLRequest(URL: url)

NSURLConnection.sendAsynchronousRequest(request, queue:NSOperationQueue.currentQueue() {
response, maybeData, error in

    // This block never gets called?
    if let data = maybeData {
        let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
        println(contents)
    } else {
        println(error.localizedDescription)
    }
}

我可以在 Playground 时间轴中看到控制台输出,但我的完成块中的 println 从未被调用...

【问题讨论】:

    标签: asynchronous callback closures swift swift-playground


    【解决方案1】:

    未调用回调的原因是 RunLoop 未在 Playground 中运行(或在 REPL 模式下)。

    使回调运行的一种有点笨拙但有效的方法是使用一个标志,然后在运行循环上手动迭代:

    // Playground - noun: a place where people can play
    
    import Cocoa
    import XCPlayground
    
    let url = NSURL(string: "http://stackoverflow.com")
    let request = NSURLRequest(URL: url)
    
    var waiting = true
    
    NSURLConnection.sendAsynchronousRequest(request, queue:NSOperationQueue.currentQueue() {
    response, maybeData, error in
        waiting = false
        if let data = maybeData {
            let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
            println(contents)
        } else {
            println(error.localizedDescription)
        }
    }
    
    while(waiting) {
        NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate())
        usleep(10)
    }
    

    这种模式经常用于需要测试异步回调的单元测试中,例如:Pattern for unit testing async queue that calls main queue on completion

    【讨论】:

      【解决方案2】:

      虽然您可以手动运行运行循环(或者,对于不需要运行循环的异步代码,使用其他等待方法,例如分派信号量),但我们在 Playground 中提供的“内置”方式来等待异步工作是导入XCPlayground 框架并设置XCPlaygroundPage.currentPage.needsIndefiniteExecution = true。如果此属性已设置,当您的顶级 Playground 源代码完成时,我们将继续旋转主运行循环,而不是在那里停止 Playground,因此异步代码有机会运行。我们最终会在默认为 30 秒的超时后终止 Playground,但如果您打开助手编辑器并显示时间线助手,则可以进行配置;超时在右下角。

      例如,在 Swift 3 中(使用 URLSession 而不是 NSURLConnection):

      import UIKit
      import PlaygroundSupport
      
      let url = URL(string: "http://stackoverflow.com")!
      
      URLSession.shared.dataTask(with: url) { data, response, error in
          guard let data = data, error == nil else {
              print(error ?? "Unknown error")
              return
          }
      
          let contents = String(data: data, encoding: .utf8)
          print(contents!)
      }.resume()
      
      PlaygroundPage.current.needsIndefiniteExecution = true
      

      或者在 Swift 2 中:

      import UIKit
      import XCPlayground
      
      let url = NSURL(string: "http://stackoverflow.com")
      let request = NSURLRequest(URL: url!)
      
      NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()) { response, maybeData, error in
          if let data = maybeData {
              let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
              println(contents)
          } else {
              println(error.localizedDescription)
          }
      }
      
      XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
      

      【讨论】:

      • 无论如何,这在 WWDC 2014 §408: Swift Playgrounds, 下半场中有介绍
      • 值得注意的是,从 DP4 开始,XCPlayground 框架现在也可用于 iOS Playgrounds。
      • 更新方法:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
      • 更新方法:import PlaygroundSupportPlaygroundPage.current.needsIndefiniteExecution = true
      【解决方案3】:
      NSURLConnection.sendAsynchronousRequest(...)    
      NSRunLoop.currentRunLoop().run()
      

      【讨论】:

        【解决方案4】:

        从 XCode 7.1 开始,XCPSetExecutionShouldContinueIndefinitely() 已弃用。现在正确的做法是首先请求无限期执行作为当前页面的属性:

        import XCPlayground
        
        XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
        

        …然后指示执行何时完成:

        XCPlaygroundPage.currentPage.finishExecution()
        

        例如:

        import Foundation
        import XCPlayground
        
        XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
        
        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.com")!) {
            result in
            print("Got result: \(result)")
            XCPlaygroundPage.currentPage.finishExecution()
        }.resume()
        

        【讨论】:

          【解决方案5】:

          此 API 在 Xcode 8 中再次更改,并移至 PlaygroundSupport

          import PlaygroundSupport
          
          PlaygroundPage.current.needsIndefiniteExecution = true
          

          Session 213 at WWDC 2016 中提到了此更改。

          【讨论】:

          • 别忘了给PlaygroundPage.current.finishExecution()打电话。
          【解决方案6】:

          XCode8、Swift3 和 iOS 10 的新 API 是,

          // import the module
          import PlaygroundSupport
          // write this at the beginning
          PlaygroundPage.current.needsIndefiniteExecution = true
          // To finish execution
          PlaygroundPage.current.finishExecution()
          

          【讨论】:

            【解决方案7】:

            Swift 3、xcode 8、iOS 10

            注意事项:

            告诉编译器操场文件需要“无限期执行”

            通过在完成处理程序中调用 PlaygroundSupport.current.completeExecution() 手动终止执行。

            您可能会遇到缓存目录问题,要解决此问题,您需要手动重新实例化 UICache.shared 单例。

            示例:

            import UIKit
            import Foundation
            import PlaygroundSupport
            
            // resolve path errors
            URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
            
            // identify that the current page requires "indefinite execution"
            PlaygroundPage.current.needsIndefiniteExecution = true
            
            // encapsulate execution completion
            func completeExecution() {
                PlaygroundPage.current.finishExecution()
            }
            
            let url = URL(string: "http://i.imgur.com/aWkpX3W.png")
            
            let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
                var image = UIImage(data: data!)
            
                // complete execution
                completeExecution()
            }
            
            task.resume()
            

            【讨论】:

              【解决方案8】:

              Swift 4、Xcode 9.0

              import Foundation
              import PlaygroundSupport
              
              PlaygroundPage.current.needsIndefiniteExecution = true
              
              let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
              
              let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                  guard error == nil else {
                      print(error?.localizedDescription ?? "")
                      return
                  }
              
                  if let data = data, let contents = String(data: data, encoding: String.Encoding.utf8) {
                      print(contents)
                  }
              }
              task.resume()
              

              【讨论】:

                猜你喜欢
                • 2022-07-27
                • 2017-11-21
                • 1970-01-01
                • 1970-01-01
                • 2015-03-08
                • 2015-12-26
                • 1970-01-01
                • 2021-12-18
                • 1970-01-01
                相关资源
                最近更新 更多