与Alamofire的NSURLSession并发请求

与Alamofire的NSURLSession并发请求

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

问题描述

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

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

当我使用 manager.request / code>他们被调度成2对(如预期,与Charles HTTP代理检查)。奇怪的是,所有的请求没有在30秒内从第一个请求完成,由于超时同时取消(即使他们还没有发送)。以下是展示行为的示意图:





这是一个预期的行为,如何确保请求在发送之前不会超时?

$ b $非常感谢!

解决方案

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



原来的AFNetworking做了一个奇妙的工作,在操作,这使得这个微不足道。但AFNetworking的 NSURLSession 实现从来没有这样做,Alamofire也没有。






您可以轻松地将请求包装在 NSOperation 子类中。例如:

  class NetworkOperation:AsynchronousOperation {

//定义属性,当你实例化
//这个对象时,将提供,并将在请求最终启动时使用
//
//在这个例子中,我将跟踪(a)URL;和(b)当请求完成时关闭调用

让URLString:String
let networkOperationCompletionHandler:(_ responseObject:Any?,error:Error?) - > ()

//我们还将跟踪所产生的请求操作,以防我们需要稍后取消它

weak var request:Alamofire.Request?

//定义捕获发出请求时使用的所有属性的init方法

init(URLString:String,networkOperationCompletionHandler:@escaping(_ responseObject:Any? ,_ error:Error?) - >()){
self.URLString = URLString
self.networkOperationCompletionHandler = networkOperationCompletionHandler
super.init()
}


//当操作实际开始时,这是将被调用的方法

override func main(){
request = Alamofire.request(URLString,方法:.get,parameters:[foo:bar])
.responseJSON {response in
// do whatever you want here;个人而言,我只是在init中传递给我的完成处理程序

self.networkOperationCompletionHandler(response.result.value,response.result.error)

//现在我完成了,完成这个操作

self.completeOperation()
}
}

//支持取消请求,万一我们需要它

override func cancel(){
request?.cancel()
super.cancel()
}
}

然后,当我想启动我的50个请求时, :

  let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2

for i在0 ..< 50 {
let operation = NetworkOperation(URLString:http://example.com/request.php?value=\(i)){responseObject,
中的错误if responseObject == nil {
//在这里处理错误

print(failed:\(error))
} else {
//更新UI来反映`responseObject`已成功完成

print(responseObject = \(responseObject!))
}
}
queue.addOperation(operation)
}

这样,这些请求将受 maxConcurrentOperationCount



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

  // 
// AsynchronousOperation.swift
//
//由2014年9月20日的Robert Ryan创建。
//版权所有(c)2014 Robert Ryan。版权所有。
//

import Foundation

///异步操作基类
///
///此类执行所有`isFinished`的必要的KVN和
///`isExecuting`用于一个并发的NSOperation子类。所以,对开发人员
///一个并发的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()
}

override public func main(){
fatalError( )
}
}

/ *
版权所有(C)2015 Apple Inc.保留所有权利。
关于本示例的许可信息,请参阅LICENSE.txt

摘要:
NSLock的扩展以简化执行关键代码。

从WWDC 2015中的高级NSOperations示例代码https://developer.apple.com/videos/play/wwdc2015/226/
从https://developer.apple.com/sample -code / wwdc / 2015 / downloads / Advanced-NSOperations.zip
* /

import Foundation

扩展NSLock {

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

func with CriticalScope< T>(块:(Void) - > T) T {
lock()
let value = block()
unlock()
返回值
}
}

这种模式还有其他可能的变化,但只是确保你(a)返回 true for asynchronous ;和(b)您提交了配置并发执行操作 / em>部分中的。


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)

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?

Thanks a lot!

解决方案

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.

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.


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

    let URLString: String
    let networkOperationCompletionHandler: (_ responseObject: Any?, _ error: Error?) -> ()

    // 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: @escaping (_ responseObject: Any?, _ error: Error?) -> ()) {
        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)

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

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
        if responseObject == nil {
            // handle error here

            print("failed: \(error)")
        } else {
            // update UI to reflect the `responseObject` finished successfully

            print("responseObject=\(responseObject!)")
        }
    }
    queue.addOperation(operation)
}

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

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 {

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

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并发请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-05 23:08