Swift 使用Alamofire的NSURLSession并发请求
我的测试应用程序出现了一些奇怪的行为。我有大约50个同时发送到同一服务器的GET请求。服务器是一个嵌入式服务器,位于一小块硬件上,资源非常有限。为了优化每个请求的性能,我将Swift 使用Alamofire的NSURLSession并发请求,swift,concurrency,nsurlsession,alamofire,nsurlsessionconfiguration,Swift,Concurrency,Nsurlsession,Alamofire,Nsurlsessionconfiguration,我的测试应用程序出现了一些奇怪的行为。我有大约50个同时发送到同一服务器的GET请求。服务器是一个嵌入式服务器,位于一小块硬件上,资源非常有限。为了优化每个请求的性能,我将Alamofire.Manager的一个实例配置如下: let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() configuration.HTTPMaximumConnectionsPerHost = 2 configuratio
Alamofire.Manager
的一个实例配置如下:
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPMaximumConnectionsPerHost = 2
configuration.timeoutIntervalForRequest = 30
let manager = Alamofire.Manager(configuration: configuration)
当我使用manager.request(…)
发送请求时,它们将以2对进行调度(正如预期的那样,通过Charles HTTP Proxy进行检查)。但奇怪的是,所有在第一次请求后30秒内未完成的请求都会因为超时而被取消(即使它们尚未发送)。下面是一个演示行为的示例:
这是一种预期的行为吗?我如何确保请求在发送之前不会超时
非常感谢 是的,这是预期的行为。一种解决方案是将您的请求包装在自定义的异步
NSOperation
子类中,然后使用操作队列的maxConcurrentOperationCount
来控制并发请求的数量,而不是HTTPMaximumConnectionsPerHost
参数
最初的AFNetworking在将请求包装到操作中方面做得很好,这使得它变得微不足道。但是AFNetworking的NSURLSession
实现从来没有做到这一点,Alamofire也没有做到
您可以轻松地将
请求
包装到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)
}
此模式还有其他可能的变体,但请确保(a)为
异步返回true
;(b)您发布必要的isFinished
和isExecuting
KVN,如中的并发执行配置操作部分所述。也许您实际想要设置的是'TimeoutiterValforResource,而不是
TimeoutiterValforRequest`?谢谢,但是我尝试了这两种方法,同样的事情不断发生。你的方法在Alamofire 4中不再有效,请更新它。你使用了什么程序来制作这张图?@NikKov我使用了photoshopWow,非常感谢Rob,得到这么好的答案并不经常发生!工作起来很有魅力。堆栈溢出上的所有用户贡献都是使用。请参阅此网页底部页脚中的链接。总之,作者保留Stack Overflow贡献的版权,但我们也授予永久许可证,允许将这些特定贡献用于任何目的,包括商业目的,唯一要求是(a)要求归属,以及(b)您将同样分享。简言之,是的,它可以自由使用。@JAHelia-不,我没有,因为我的异步操作
在执行后将闭包设置为nil
,从而解决了任何强引用循环。@fam-在这种异步操作模式中,在完成此操作后,您可以创建一个新的操作来执行所需的操作,并使其依赖于单个网络请求的所有单个操作。@FAM“我看到您设置了maxConcurrentOperationCount=2
,但您确实调用了50次请求…”这就是问题的关键:OP想要将50个请求排队,但决不能同时运行两个以上。maxConcurrentOperationCount
简单地指示在任何给定时间可以运行多少。(您不希望同时运行太多的请求,因为(a)URLSession
一次只能运行这么多个请求,因此您可能会面临后一个请求超时的风险;以及(b)内存问题。)在排队处理多个请求时,上述方法可以实现可控的并发度。
//
// 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 {
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`")
}
}
/*
Abstract:
An extension to `NSLocking` to simplify executing critical code.
Adapted from Advanced NSOperations sample code in WWDC 2015 https://developer.apple.com/videos/play/wwdc2015/226/
Adapted from https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip
*/
import Foundation
extension NSLocking {
/// Perform closure within lock.
///
/// An extension to `NSLocking` to simplify executing critical code.
///
/// - parameter block: The closure to be performed.
func withCriticalScope<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}