Вопрос

Многие методы 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 в моем блоке завершения никогда не вызываются...

Это было полезно?

Решение

Хотя вы можете запустить цикл выполнения вручную (или, для асинхронного кода, который не требует цикла выполнения, использовать другие методы ожидания, такие как семафоры отправки), «встроенный» способ, который мы предоставляем в игровых площадках для ожидания асинхронной работы, заключается в следующем: импортировать XCPlayground рамки и набор XCPlaygroundPage.currentPage.needsIndefiniteExecution = true.Если это свойство было установлено, то когда ваш исходный код игровой площадки верхнего уровня завершится, вместо того, чтобы останавливать игровую площадку на этом месте, мы продолжим вращать основной цикл выполнения, поэтому асинхронный код имеет возможность запуститься.В конечном итоге мы закроем игровую площадку после тайм-аута, который по умолчанию равен 30 секундам, но который можно настроить, если вы откроете редактор помощников и отобразите помощник временной шкалы;тайм-аут находится в правом нижнем углу.

Например, в 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

Другие советы

Эта API снова изменилась в XCode 8, и он был перемещен в PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
.

Это изменение было упомянуто в сеанс 213 в WWDC 2016 .

Начиная с 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()

Причина, по которой обратные вызовы не вызываются, заключается в том, что runloop не работает в детской площадке (или в режиме reft для этого значения).

Несколько янник, но эффективен, способ выполнить обратные вызовы работать с флагом, а затем вручную итерации на undloop:

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

Этот шаблон часто использовался в модульных тестах, которые необходимо тестировать async обратные вызовы, например:

Новые API для XCode 8, Swift 3 и iOS 10 есть,

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

<Сильные> Примечания:

Скажите компилятору, что файл детской площадки требует «неопределенного выполнения»

вручную завершайте выполнение через вызов для PlaygroundSupport.current.completeExecution() в рамках вашего обработчика завершения.

Вы можете столкнуться с проблемами с каталогом кэша и решить это, вам нужно будет вручную повторно создавать UICACHE. Собрать Singleton.

<Сильный> Пример:

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()
.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top