与Alamofire的NSURLSession并发请求
我正在使用我的testing应用程序遇到一些奇怪的行为。 我有大约50个同时发送到同一个服务器的GET请求。 服务器是一个embedded式服务器上的一小部分硬件资源非常有限。 为了优化每个请求的性能,我configuration一个Alamofire.Manager
实例如下:
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() configuration.HTTPMaximumConnectionsPerHost = 2 configuration.timeoutIntervalForRequest = 30 let manager = Alamofire.Manager(configuration: configuration)
当我通过manager.request(...)
发送请求时,它们会以2对的forms发送(如预期的那样,使用Charles HTTP Proxy进行检查)。 奇怪的是,所有在第一个请求30秒内没有完成的请求,由于同时超时而被取消(即使它们还没有被发送)。 下面是一个展示行为的例子:
这是一个预期的行为,我怎样才能确保请求不会得到超时之前,他们甚至被发送?
非常感谢!
是的,这是预期的行为。 一种解决scheme是将自己的请求包装在自定义asynchronousNSOperation
子类中,然后使用操作队列的maxConcurrentOperationCount
来控制并发请求的数量,而不是HTTPMaximumConnectionsPerHost
参数。
最初的AFNetworking做了一个很好的工作,包装在操作请求,这使得这个微不足道的。 但AFNetworking的NSURLSession
实现从来没有这样做过,Alamofire也没有。
您可以轻松地将Request
包装在NSOperation
子类中。 例如:
class NetworkOperation: AsynchronousOperation { // define properties to hold everything that you'll supply when you instantiate // this object and will be used when the request finally starts // // in this example, I'll keep track of (a) URL; and (b) closure to call when request is done private let urlString: String private var networkOperationCompletionHandler: ((_ responseObject: Any?, _ error: Error?) -> Void)? // we'll also keep track of the resulting request operation in case we need to cancel it later weak var request: Alamofire.Request? // define init method that captures all of the properties to be used when issuing the request init(urlString: String, networkOperationCompletionHandler: ((_ responseObject: Any?, _ error: Error?) -> Void)? = nil) { self.urlString = urlString self.networkOperationCompletionHandler = networkOperationCompletionHandler super.init() } // when the operation actually starts, this is the method that will be called override func main() { request = Alamofire.request(urlString, method: .get, parameters: ["foo" : "bar"]) .responseJSON { response in // do whatever you want here; personally, I'll just all the completion handler that was passed to me in `init` self.networkOperationCompletionHandler?(response.result.value, response.result.error) self.networkOperationCompletionHandler = nil // now that I'm done, complete this operation self.completeOperation() } } // we'll also support canceling the request, in case we need it override func cancel() { request?.cancel() super.cancel() } }
然后,当我想发起我的50个请求,我会做这样的事情:
let queue = OperationQueue() queue.maxConcurrentOperationCount = 2 for i in 0 ..< 50 { let operation = NetworkOperation(urlString: "http://example.com/request.php?value=\(i)") { responseObject, error in guard let responseObject = responseObject else { // handle error here print("failed: \(error?.localizedDescription ?? "Unknown error")") return } // update UI to reflect the `responseObject` finished successfully print("responseObject=\(responseObject)") } queue.addOperation(operation) }
这样,这些请求将受到maxConcurrentOperationCount
约束,我们不必担心任何请求超时。
这是AsynchronousOperation
基类的一个例子,它处理与asynchronous/并发NSOperation
子类关联的KVN:
// // AsynchronousOperation.swift // // Created by Robert Ryan on 9/20/14. // Copyright (c) 2014 Robert Ryan. All rights reserved. // 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() } override public func main() { fatalError("subclasses must override `main`") } } /* Copyright (C) 2015 Apple Inc. All Rights Reserved. See LICENSE.txt for this sample's licensing information Abstract: An extension to `NSLock` to simplify executing critical code. From Advanced NSOperations sample code in WWDC 2015 https://developer.apple.com/videos/play/wwdc2015/226/ From https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip */ import Foundation 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: (Void) -> T) -> T { lock() let value = block() unlock() return value } }
这种模式还有其他可能的变化,但是只要确保你(a)对asynchronous
返回true
; (b)按照“ 并发编程指南:操作队列”中的configuration并发执行操作部分所述,发布必要的isFinished
并isExecuting
KVN。