Синхронный запрос URL на Swift 2

У меня есть этот код здесь, чтобы выполнить синхронный запрос URL-адреса на 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)
  }

но функция NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error) была устаревшей, и я не вижу, как можно выполнять синхронные запросы в Swift, потому что альтернатива является асинхронной. Очевидно, Apple отказалась от единственной функции, которая может делать это синхронно.

Как я могу это сделать?

Ответы

Ответ 1

Есть причина, по которой усталость - для нее просто бесполезно. Вы должны избегать синхронных сетевых запросов как чума. У этого есть две основные проблемы и только одно преимущество (это легко использовать.. но не асинк также?):

  • Запрос блокирует ваш пользовательский интерфейс, если он не вызывается из другого потока, но если вы это сделаете, почему бы не использовать асинхронный обработчик сразу?
  • Невозможно отменить этот запрос, кроме случаев, когда он сам по себе

Вместо этого просто используйте асинхронный запрос:

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

iOS9 Устаревание

Так как в iOS9 этот метод устарел, я предлагаю вам вместо этого использовать 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)
}

Ответ 2

Если вы действительно хотите сделать это синхронно, вы всегда можете использовать семафор:

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

РЕДАКТИРОВАТЬ: Добавьте некоторых хакеров! поэтому код работает, не делайте этого в производственном коде

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

Ответ 3

На основании ответа @fpg1503 я сделал простое расширение в 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?)

    }

}

Затем вы просто вызываете:

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

Ответ 4

Синхронные запросы иногда отлично подходят для фоновых потоков. Иногда у вас сложная, невозможная замена базы кода, заполненной асинхронными запросами, и т.д. Тогда есть небольшой запрос, который не может быть сложен в текущую систему как async. Если синхронизация не удалась, вы не получите никаких данных. Просто. Он имитирует работу файловой системы.

Уверен, что он не охватывает всевозможные события, но есть много вариантов, которые не рассматриваются в асинхронном режиме.