【问题标题】:How to make simultaneous https requests in Swift 3如何在 Swift 3 中同时发出 https 请求
【发布时间】:2016-11-12 01:37:31
【问题描述】:

我在执行 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()

【问题讨论】:

标签: http concurrency swift3 nsoperation nsoperationqueue


【解决方案1】:

一个问题是您的操作只是启动请求,但由于请求是异步执行的,操作是立即完成的,而不是真正等待请求完成。在异步请求完成之前,您不想完成操作。

如果您想对操作队列执行此操作,诀窍是您必须继承 Operation 并为 isExecutingisFinished 执行必要的 KVO。然后,您在启动请求时更改 isExecuting,在完成请求时更改 isFinished,并为两者关联 KVO。这一切都在Concurrency Programming Guide: Defining a Custom Operation Object 中进行了概述,特别是在Configuring Operations for Concurrent Execution 部分。 (注意,本指南有点过时(它指的是isConcurrent 属性,已被替换为isAsynchronous;它专注于Objective-C 等),但它向您介绍了问题。

无论如何,这是一个抽象类,我用它来封装所有这些愚蠢的异步操作:

/// 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 lock = NSLock()

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

    private var _finished: Bool = false
    override private(set) public var isFinished: Bool {
        get {
            return lock.synchronize { _finished }
        }
        set {
            willChangeValue(forKey: "isFinished")
            lock.synchronize { _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
            isFinished = true
        }
    }

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

        isExecuting = true

        main()
    }
}

我使用这个 Apple 扩展 NSLocking 来确保我同步上面的状态更改(他们的扩展名为 withCriticalSectionNSLock,但这是一个稍微更通用的演绎,适用于任何事情符合NSLocking 并处理抛出错误的闭包):

extension NSLocking {

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

    func synchronize<T>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

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

class NetworkOperation: AsynchronousOperation {
    var task: URLSessionTask!

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

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

    override func main() {
        task.resume()
    }

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

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

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

let url = URL(string: "http://...")!
let operation = NetworkOperation(session: .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

注意,在上面的例子中,我添加了第二个操作,它只是打印了一些东西,使它依赖于第一个操作,以说明第一个操作直到网络请求完成后才完成。

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

【讨论】:

  • 非常感谢!但是你不会错过AsynchronousOperation 类中的override public func cancel(),而你有isFinished = true 并称它为超级吗?否则,如果取消,操作将保留在队列中:)
  • 文档说,“取消操作不会立即强制它停止正在做的事情。 ...您的代码必须明确检查此属性中的值并根据需要中止。”所以,不,AsynchronousOperation 的默认实现仅仅完成操作是不明智的,而是它的子类必须决定如何以及何时停止实际的底层任务,然后才完成操作。如果子类由于某种原因没有响应取消,那么操作也不应该过早完成。
  • 嗨,Rob,谢谢 :) 好的,那么你不会在 NetworkOperation 中接受我建议的更改吗?谢谢你的时间:)
  • NetworkOperation 确实实现了cancel,它取消了任务。当一个网络任务被取消时,它的完成处理程序会被调用(这是我们调用completeOperation的地方,它完成了任务)。
  • 好的,我使用了 Operation 的 completionBlock,而不是您拥有的 requestCompletionHandler。也许这会有所作为。对于那个很抱歉。但是我在我的子类override func cancel() 中添加了对completeOperation() 的调用,然后内存中没有任何操作。谢谢你的帮助:)
猜你喜欢
  • 2018-07-16
  • 2016-09-30
  • 1970-01-01
  • 2018-03-26
  • 2018-03-25
  • 1970-01-01
  • 2012-12-14
  • 2017-03-22
  • 1970-01-01
相关资源
最近更新 更多