52

Я испытываю странное поведение с моим тестовым приложением. У меня около 50 одновременных запросов GET, которые я отправляю на тот же сервер. Сервер представляет собой встроенный сервер на небольшом аппаратном обеспечении с очень ограниченными ресурсами. Для того, чтобы оптимизировать производительность для каждого отдельного запроса, настроить один экземпляр Alamofire.Manager следующим образом:NSURLSession одновременные запросы с Alamofire

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

Когда я посылаю запросы с manager.request(...) они получают пересылаемых в парах 2 (как и ожидалось, проверил с Чарльзом HTTP Proxy). Странно, однако, что все запросы, которые не завершились в течение 30 секунд с первого запроса, будут отменены из-за тайм-аута в одно и то же время (даже если они еще не были отправлены). Вот иллюстрация демонстрации поведения:

concurrent request illustration

Является ли это ожидаемое поведение, и как я могу убедиться, что запросы не получит тайм-аут, прежде чем они даже послали?

Большое спасибо!

+0

Возможно, вы действительно хотите установить ' timeoutIntervalForResource', а не' timeoutIntervalForRequest'? – mattt

+0

Спасибо, но я попробовал и то и другое, и то же самое продолжалось. – Hannes

ответ

104

Да, это ожидаемое поведение. Одним из решений является обернуть ваши запросы в пользовательский, асинхронный подкласс NSOperation, а затем использовать 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 базовый класс, который заботится о КВН, связанной с асинхронным/параллельного NSOperation подкласса:

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

Существуют и другие возможные варианты этой модели, но только убедитесь, что вы (а) вернуть true для asynchronous; и (b) вы публикуете необходимые isFinished и isExecuting KVN, как описано в разделе Конфигурирование операций для параллельного выполнения раздела Concurrency Programming Guide: Operation Queues.

+1

Ничего себе, большое спасибо Робу, не бывает так часто, чтобы получить такой отличный ответ! Работает как шарм. – Hannes

+0

wow большой ответ –

+0

Превосходный ответ. Действительно полезно !!! Спасибо!!! – Sendoa