如何在Swift 3中同时进行https请求

edu*_*rdo 2 concurrency http nsoperation nsoperationqueue swift3

我有执行https请求的问题,如果请求没有任何错误,我从来没有得到消息,这是一个命令行工具应用程序,我有一个允许http请求的plist,我总是看到完成块.

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()
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 7

一个问题是您的操作仅仅是启动请求,但由于请求是异步执行的,因此操作立即完成,而不是实际等待请求完成.在异步请求完成之前,您不希望完成操作.

如果你想运行队列要做到这一点,关键是,你必须继承NSOperation,返回trueisAsynchronous.然后,isExecuting当您启动请求时,isFinished当您完成请求时,您需要更改这两个请求的KVO." 并发编程指南:定义自定义操作对象"中概述了这一点,特别是在"为并发执行配置操作"部分中.(注意,本指南有点过时(它指的是isConcurrent已被替换的属性isAsynchronous;它专注于Objective-C;等等),但它向您介绍了这些问题.

无论如何,这是一个抽象类,我用来封装所有这些异步操作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()
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用这个Apple扩展来NSLock确保我同步上面的状态更改:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我可以创建一个NetworkOperation使用它:

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

}
Run Code Online (Sandbox Code Playgroud)

无论如何,完成后,我现在可以为网络请求创建操作,例如:

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
Run Code Online (Sandbox Code Playgroud)

注意,在上面的例子中,我添加了第二个操作,只是打印了一些东西,使其依赖于第一个操作,以说明在网络请求完成之前第一个操作没有完成.

显然,您通常不会使用waitUntilAllOperationsAreFinished原始示例,也不会使用我的示例中的waitUntilFinished选项addOperations.但是因为您正在处理一个命令行应用程序,在这些请求完成之前您不想退出,所以这种模式是可以接受的.(我只是为了未来的读者而提到这一点,他们对自由飞行的使用感到惊讶waitUntilFinished,这通常是不可取的.)