How to make simultaneous https requests in Swift 3

有些话、适合烂在心里 提交于 2019-12-13 12:15:54

问题


I'm having problems to execute a https requests, if the request don't have any error i never get the message, this is a command line tool application and i have a plist to allow http requests, i always see the completion block.

typealias escHandler = ( URLResponse?, Data? ) -> Void

func getRequest(url : URL, _ handler : @escaping escHandler){    
let session = URLSession.shared
var request = URLRequest(url:url)
request.cachePolicy = .reloadIgnoringLocalCacheData
request.httpMethod = "GET"
let task = session.dataTask(with: url ){ (data,response,error) in
        handler(response,data)
}

task.resume()
}


func startOp(action : @escaping () -> Void) -> BlockOperation{

let exOp = BlockOperation(block: action)    
exOp.completionBlock = {

print("Finished")

}
return exOp
}

     for sUrl in textFile.components(separatedBy: "\n"){
     let url = URL(string: sUrl)!

        let queu = startOp {
            getRequest(url: url){  response, data  in

                print("REACHED")



            }

        }
      operationQueue.addOperation(queu)
      operationQueue.waitUntilAllOperationsAreFinished()

回答1:


One problem is that your operation is merely starting the request, but because the request is performed asynchronously, the operation is immediately completing, not actually waiting for the request to finish. You don't want to complete the operation until the asynchronous request is done.

If you want to do this with operation queues, the trick is that you must subclass NSOperation, return true from isAsynchronous. And you then change isExecuting when you start the request and isFinished when you finish the request, doing the necessary KVO for both of those. This is all outlined in the Concurrency Programming Guide: Defining a Custom Operation Object, notably in the Configuring Operations for Concurrent Execution section. (Note, this guide is a little outdated (it refers to the isConcurrent property, which has been replaced is isAsynchronous; it's focusing on Objective-C; etc.), but it introduces you to the issues.

Anyway, This is an abstract class that I use to encapsulate all of this asynchronous operation silliness:

//  AsynchronousOperation.swift

import Foundation

/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
///   necessary and then ensuring that `completeOperation()` is called; or
///   override `cancel` method, calling `super.cancel()` and then cleaning-up
///   and ensuring `completeOperation()` is called.

public class AsynchronousOperation : Operation {

    override public var isAsynchronous: Bool { return true }

    private let stateLock = NSLock()

    private var _executing: Bool = false
    override private(set) public var isExecuting: Bool {
        get {
            return stateLock.withCriticalScope { _executing }
        }
        set {
            willChangeValue(forKey: "isExecuting")
            stateLock.withCriticalScope { _executing = newValue }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _finished: Bool = false
    override private(set) public var isFinished: Bool {
        get {
            return stateLock.withCriticalScope { _finished }
        }
        set {
            willChangeValue(forKey: "isFinished")
            stateLock.withCriticalScope { _finished = newValue }
            didChangeValue(forKey: "isFinished")
        }
    }

    /// Complete the operation
    ///
    /// This will result in the appropriate KVN of isFinished and isExecuting

    public func completeOperation() {
        if isExecuting {
            isExecuting = false
        }

        if !isFinished {
            isFinished = true
        }
    }

    override public func start() {
        if isCancelled {
            isFinished = true
            return
        }

        isExecuting = true

        main()
    }
}

And I use this Apple extension to NSLock to make sure I synchronize the state changes in the above:

extension NSLock {

    /// Perform closure within lock.
    ///
    /// An extension to `NSLock` to simplify executing critical code.
    ///
    /// - parameter block: The closure to be performed.

    func withCriticalScope<T>(block: () -> T) -> T {
        lock()
        let value = block()
        unlock()
        return value
    }
}

Then, I can create a NetworkOperation which uses that:

class NetworkOperation: AsynchronousOperation {

    let url: URL
    let session: URLSession
    let requestCompletionHandler: (Data?, URLResponse?, Error?) -> ()

    init(session: URLSession, url: URL, requestCompletionHandler: @escaping (Data?, URLResponse?, Error?) -> ()) {
        self.session = session
        self.url = url
        self.requestCompletionHandler = requestCompletionHandler

        super.init()
    }

    private weak var task: URLSessionTask?

    override func main() {
        let task = session.dataTask(with: url) { data, response, error in
            self.requestCompletionHandler(data, response, error)
            self.completeOperation()
        }
        task.resume()
        self.task = task
    }

    override func cancel() {
        task?.cancel()
        super.cancel()
    }

}

Anyway, having done that, I can now create operations for network requests, e.g.:

let queue = OperationQueue()
queue.name = "com.domain.app.network"

let url = URL(string: "http://...")!
let operation = NetworkOperation(session: URLSession.shared, url: url) { data, response, error in
    guard let data = data, error == nil else {
        print("\(error)")
        return
    }

    let string = String(data: data, encoding: .utf8)
    print("\(string)")
    // do something with `data` here
}

let operation2 = BlockOperation {
    print("done")
}

operation2.addDependency(operation)

queue.addOperations([operation, operation2], waitUntilFinished: false) // if you're using command line app, you'd might use `true` for `waitUntilFinished`, but with standard Cocoa apps, you generally would not

Note, in the above example, I added a second operation that just printed something, making it dependent on the first operation, to illustrate that the first operation isn't completed until the network request is done.

Obviously, you would generally never use the waitUntilAllOperationsAreFinished of your original example, nor the waitUntilFinished option of addOperations in my example. But because you're dealing with a command line app that you don't want to exit until these requests are done, this pattern is acceptable. (I only mention this for the sake of future readers who are surprised by the free-wheeling use of waitUntilFinished, which is generally inadvisable.)



来源:https://stackoverflow.com/questions/40558679/how-to-make-simultaneous-https-requests-in-swift-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!