使用 Alamofire 的 NSURLSession 并发请求 [英] NSURLSession concurrent requests with Alamofire

查看:42
本文介绍了使用 Alamofire 的 NSURLSession 并发请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的测试应用出现了一些奇怪的行为.我有大约 50 个同时发送到同一台服务器的 GET 请求.服务器是资源非常有限的一小块硬件上的嵌入式服务器.为了优化每个请求的性能,我配置了一个 Alamofire.Manager 实例如下:

I'm experiencing some strange behaviour with my test app. I've about 50 simultaneous GET requests that I send to the same server. The server is an embedded server on a small piece of hardware with very limited resources. In order to optimize the performance for each single request, I configure one instance of Alamofire.Manager as follows:

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPMaximumConnectionsPerHost = 2
configuration.timeoutIntervalForRequest = 30
let manager = Alamofire.Manager(configuration: configuration)

当我使用 manager.request(...) 发送请求时,它们会成对发送 2 个(如预期的那样,使用 Charles HTTP 代理进行检查).但奇怪的是,所有未在第一个请求后 30 秒内完成的请求,都因超时同时被取消(即使它们尚未发送).这是展示行为的插图:

When I send the requests with manager.request(...) they get dispatched in pairs of 2 (as expected, checked with Charles HTTP Proxy). The weird thing though is, that all requests which didn't finish within 30 seconds from the first request, get cancelled because of the timeout at the same time (even if they haven't been sent yet). Here is an illustration showcasing the behaviour:

这是预期的行为吗?我怎样才能确保请求在发送之前不会超时?

Is this an expected behaviour and how can I make sure that the requests won't get the timeout before they are even sent?

非常感谢!

推荐答案

是的,这是预期的行为.一种解决方案是将您的请求包装在自定义的异步 NSOperation 子类中,然后使用操作队列的 maxConcurrentOperationCount 来控制并发请求的数量而不是 HTTPMaximumConnectionsPerHost 参数.

Yes, this is expected behavior. One solution is to wrap your requests in custom, asynchronous NSOperation subclass, and then use the maxConcurrentOperationCount of the operation queue to control the number of concurrent requests rather than the HTTPMaximumConnectionsPerHost parameter.

最初的 AFNetworking 在将请求包装在操作中做得非常出色,这使得这变得微不足道.但是 AFNetworking 的 NSURLSession 实现从来没有这样做过,Alamofire 也没有这样做.

The original AFNetworking did a wonderful job wrapping the requests in operations, which made this trivial. But AFNetworking's NSURLSession implementation never did this, nor does Alamofire.

您可以轻松地将 Request 包装在 NSOperation 子类中.例如:

You can easily wrap the Request in an NSOperation subclass. For example:

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 个请求时,我会这样做:

Then, when I want to initiate my 50 requests, I'd do something like this:

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 的约束,我们不必担心任何请求超时..

That way, those requests will be constrained by the maxConcurrentOperationCount, and we don't have to worry about any of the requests timing out..

这是一个示例 AsynchronousOperation 基类,它负责处理与异步/并发 NSOperation 子类关联的 KVN:

This is an example AsynchronousOperation base class, which takes care of the KVN associated with asynchronous/concurrent NSOperation subclass:

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

此模式还有其他可能的变体,但只要确保您 (a) 为 asynchronous 返回 true;(b) 您发布必要的 isFinishedisExecuting KVN,如并发编程指南: 操作队列.

There are other possible variations of this pattern, but just ensure that you (a) return true for asynchronous; and (b) you post the necessary isFinished and isExecuting KVN as outlined the Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide: Operation Queues.

这篇关于使用 Alamofire 的 NSURLSession 并发请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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