如何在Swift 3中同时发出https请求 [英] How to make simultaneous https requests in Swift 3

查看:374
本文介绍了如何在Swift 3中同时发出https请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

task.resume()
}


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

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

print(Finished)

}
return exOp
}

用于textFile.components中的sUrl(separatedBy:\\\
){
let url = URL(string:sUrl)!

let queu = startOp {
getRequest(url:url){response,
中的数据
print(REACHED)



}

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

解决方案

一个问题是你的操作只是启动请求,但是因为请求是异步执行的,完成,而不是实际上等待请求完成。您不希望在异步请求完成之前完成操作。



如果您想使用操作队列执行此操作,诀窍是您必须将 NSOperation ,return true isAsynchronous 。然后,当您在启动请求时更改 isExecuting ,并在完成请求时更改 isFinished ,为两者执行必要的KVO那些。这一点在 并发编程指南:定义自定义操作对象 ,特别是在 配置并发执行操作 部分。 (注意,本指南有点过时了(它指的是 isConcurrent 属性,它已被替换为 isAsynchronous ;它是一个抽象类,我用来封装所有这些异步的操作silliness:

  // AsynchronousOperation.swift 

import Foundation

///异步操作基类
///
///这个类为并发的NSOperation执行`isFinished`和
///`isExecuting`的所有必要的KVN,子类,所以,开发人员
///一个并发的NSOperation子类,你改为子类这个类:
///
///必须覆盖`main()`任务启动异步任务;
///
///当异步任务完成时必须调用`completeOperation()`函数;
///
/// - 可选地,定期检查'self.cancelled'状态,执行任何清除
///必要,然后确保调用completeOperation()或
/// override`cancel`方法,调用`super.cancel()`然后清理
///并确保`completeOperation()`被调用。

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

///完成操作
///
///这将导致适当的KVN isFinished和isExecuting

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

如果!isFinished {
isFinished = true
}
}

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

isExecuting = true

main()
}
}


b $ b

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

  extension NSLock {

///在锁内执行闭包。
///
///一个'NSLock'的扩展来简化执行关键代码。
///
/// - 参数块:要执行的闭包。

func with CriticalScope< T>(块:() - > T) - > T {
lock()
let value = block()
unlock()
返回值
}
}
NetworkOperation
,它使用:

$

b
$ b

  class NetworkOperation:AsynchronousOperation {

let url:URL
让session:URLSession
让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()
}

私有弱变量任务: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()
}

}

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

  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) )
//用`data`在这里做一些事情
}

let operation2 = BlockOperation {
print(done)
}

operation2.addDependency(operation)

queue.addOperations([operation,operation2],waitUntilFinished:false)//如果你使用命令行应用程序,你可能会使用`true` for`waitUntilFinished`,但是使用标准的Cocoa应用程序,通常不会

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



显然,你通常不会使用原始示例的 waitUntilAllOperationsAreFinished ,也不会使用 waitUntilFinished code> addOperations 。但是因为你在处理一个命令行应用程序,你不想退出,直到这些请求完成,这种模式是可以接受的。 (我只是提到这是为了未来的读者对于自由使用 waitUntilFinished 感到惊讶,这通常是不合适的。)


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

解决方案

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

这篇关于如何在Swift 3中同时发出https请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆