Pergunta

Muitos métodos Cocoa e CocoaTouch têm retornos de chamada de conclusão implementados como blocos em Objective-C e Closures em Swift.No entanto, ao experimentá-los no Playground, a conclusão nunca é chamada.Por exemplo:

// 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)
    }
}

Posso ver a saída do console na minha linha do tempo do Playground, mas o println no meu bloco de conclusão nunca são chamados ...

Foi útil?

Solução

Embora você possa executar um loop de execução manualmente (ou, para código assíncrono que não requer um loop de execução, use outros métodos de espera, como semáforos de despacho), a maneira "incorporada" que fornecemos em playgrounds para aguardar o trabalho assíncrono é importar o XCPlayground estrutura e conjunto XCPlaygroundPage.currentPage.needsIndefiniteExecution = true.Se esta propriedade tiver sido definida, quando a fonte do playground de nível superior terminar, em vez de parar o playground ali, continuaremos a girar o loop de execução principal, para que o código assíncrono tenha a chance de ser executado.Eventualmente encerraremos o playground após um tempo limite padrão de 30 segundos, mas que pode ser configurado se você abrir o editor assistente e mostrar o assistente da linha do tempo;o tempo limite está no canto inferior direito.

Por exemplo, em Swift 3 (usando URLSession em vez de 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

Ou em 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

Outras dicas

Esta API mudou novamente no Xcode 8 e foi movida para o PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Essa mudança foi mencionada em Sessão 213 na WWDC 2016.

A partir do XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() está obsoleto.A maneira correta de fazer isso agora é primeiro solicitar a execução indefinida como propriedade da página atual:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

…então indique quando a execução terminou com:

XCPlaygroundPage.currentPage.finishExecution()

Por exemplo:

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()

A razão pela qual os retornos de chamada não são chamados é porque o RunLoop não está sendo executado no Playground (ou no modo REPL).

Uma maneira um tanto complicada, mas eficaz, de fazer os retornos de chamada funcionarem é com um sinalizador e, em seguida, iterando manualmente no runloop:

// 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)
}

Este padrão tem sido frequentemente usado em testes unitários que precisam testar retornos de chamada assíncronos, por exemplo: Padrão para fila assíncrona de teste de unidade que chama a fila principal após a conclusão

As novas APIs para XCode8, Swift3 e iOS 10 são,

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

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()

Swift 3, xcode 8, iOS 10

Notas:

Diga ao compilador que o arquivo do playground requer “execução indefinida”

Encerre manualmente a execução por meio de uma chamada para PlaygroundSupport.current.completeExecution() dentro do seu manipulador de conclusão.

Você pode ter problemas com o diretório de cache e, para resolver isso, precisará reinstanciar manualmente o singleton UICache.shared.

Exemplo:

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()
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top