如何在Swift 3中同时发出https请求 [英] How to make simultaneous https requests in Swift 3
问题描述
我遇到问题执行https请求,如果请求没有任何错误,我从来没有得到消息,这是一个命令行工具应用程序,我有一个plist允许http请求,我总是看到完成块。
typealias escHandler =(URLResponse ?, Data?) - > Void
pre>
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()
解决方案一个问题是你的操作只是启动请求,但是因为请求是异步执行的,完成,而不是实际上等待请求完成。您不希望在异步请求完成之前完成操作。
如果您想使用操作队列执行此操作,诀窍是您必须将
NSOperation
,returntrue
从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()
返回值
}
}
$ c然后,我可以创建一个NetworkOperation
,它使用: $ b
$ bclass 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> addOperationswaitUntilFinished
感到惊讶,这通常是不合适的。)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
, returntrue
fromisAsynchronous
. And you then changeisExecuting
when you start the request andisFinished
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 theisConcurrent
property, which has been replaced isisAsynchronous
; 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 thewaitUntilFinished
option ofaddOperations
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 ofwaitUntilFinished
, which is generally inadvisable.)这篇关于如何在Swift 3中同时发出https请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!