web-dev-qa-db-fra.com

Demande d'URL synchrone sur Swift 2

J'ai ce code de ici pour faire une demande synchrone d'une URL sur Swift 2.

  func send(url: String, f: (String)-> ()) {
    var request = NSURLRequest(URL: NSURL(string: url)!)
    var response: NSURLResponse?
    var error: NSErrorPointer = nil
    var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error)
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
  }

mais la fonction NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error) était obsolète et je ne vois pas comment on peut faire des requêtes synchrones sur Swift, car l'alternative est asynchrone. Apparemment Apple a déconseillé la seule fonction qui peut le faire de manière synchrone.

Comment puis je faire ça?

17
SpaceDog

Il y a une raison derrière la dépréciation - elle est tout simplement inutile. Vous devez éviter les demandes de réseau synchrone comme un fléau. Il a deux problèmes principaux et un seul avantage (il est facile à utiliser .. mais n'est-il pas aussi asynchrone?):

  • La requête bloque votre interface utilisateur si elle n'est pas appelée à partir d'un thread différent, mais si vous le faites, pourquoi ne pas utiliser immédiatement le gestionnaire asynchrone?
  • Il n'y a aucun moyen d'annuler cette demande, sauf en cas d'erreur de sa propre initiative.

Au lieu de cela, utilisez simplement une demande asynchrone:

NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in

    // Handle incoming data like you would in synchronous request
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
})

Dépréciation iOS9

Étant donné que dans iOS9, cette méthode est obsolète, je vous suggère d'utiliser plutôt NSURLSession:

let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in

    // Handle incoming data like you would in synchronous request
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
}
10
Jiri Trecak

Si vous vraiment voulez le faire de manière synchrone, vous pouvez toujours utiliser un sémaphore:

func send(url: String, f: (String) -> Void) {
    var request = NSURLRequest(URL: NSURL(string: url)!)
    var error: NSErrorPointer = nil
    var data: NSData

    var semaphore = dispatch_semaphore_create(0)

    try! NSURLSession.sharedSession().dataTaskWithRequest(request) { (responseData, _, _) -> Void in
        data = responseData! //treat optionals properly
        dispatch_semaphore_signal(semaphore)
    }.resume()

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
}

EDIT: Ajoutez un peu de hackish! pour que le code fonctionne, ne le faites pas dans le code de production

Swift 3.0+ (3.0, 3.1, 3.2, 4.0)

func send(url: String, f: (String) -> Void) {
    guard let url = URL(string: url) else {
        print("Error! Invalid URL!") //Do something else
        return
    }

    let request = URLRequest(url: url)
    let semaphore = DispatchSemaphore(value: 0)

    var data: Data? = nil

    URLSession.shared.dataTask(with: request) { (responseData, _, _) -> Void in
        data = responseData
        semaphore.signal()
    }.resume()

    semaphore.wait(timeout: .distantFuture)

    let reply = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
    f(reply)
}
39
fpg1503

Sur la base de la réponse @ fpg1503, j'ai fait une extension simple en Swift 3:

extension URLSession {

    func synchronousDataTask(with request: URLRequest) throws -> (data: Data?, response: HTTPURLResponse?) {

        let semaphore = DispatchSemaphore(value: 0)

        var responseData: Data?
        var theResponse: URLResponse?
        var theError: Error?

        dataTask(with: request) { (data, response, error) -> Void in

            responseData = data
            theResponse = response
            theError = error

            semaphore.signal()

        }.resume()

        _ = semaphore.wait(timeout: .distantFuture)

        if let error = theError {
            throw error
        }

        return (data: responseData, response: theResponse as! HTTPURLResponse?)

    }

}

Ensuite, vous appelez simplement:

let (data, response) = try URLSession.shared.synchronousDataTask(with: request)
9
Matej Ukmar

Les requêtes synchrones sont parfois correctes sur les threads d'arrière-plan. Parfois, vous avez une base de code compliquée, impossible à changer, pleine de demandes asynchrones, etc. Ensuite, il y a une petite demande qui ne peut pas être pliée dans le système actuel comme async. Si la synchronisation échoue, vous n'obtenez aucune donnée. Facile. Il imite le fonctionnement du système de fichiers.

Bien sûr, cela ne couvre pas toutes sortes d'éventualités, mais il existe également de nombreuses éventualités non couvertes par l'async.

9
Tom Andersen