NSURLSession samtidige forespørsler med Alamofire

stemmer
59

Jeg opplever noe merkelig oppførsel med min test app. Jeg har ca 50 samtidige GET-forespørsler som jeg sender til den samme serveren. Serveren er en innebygd server på et lite stykke maskinvare med svært begrensede ressurser. For å optimalisere ytelsen for hver enkelt forespørsel, konfigurerer en forekomst av Alamofire.Managersom følger:

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

Når jeg sender forespørsler til manager.request(...)de blir sendt i par på to (som forventet, sjekket med Charles HTTP Proxy). Det merkelige er skjønt, at alle forespørsler som ikke fullførte innen 30 sekunder fra første forespørsel, bli avlyst på grunn av tidsavbrudd på samme tid (selv om de ikke har blitt sendt ennå). Her er en illustrasjon utstillingsvindu atferden:

samtidig

Er dette en forventet atferd og hvordan kan jeg være sikker på at forespørslene ikke vil få timeout før de sendte?

Takk så mye!

Publisert på 19/11/2014 klokken 16:26
kilden bruker
På andre språk...                            


1 svar

stemmer
113

Ja, dette er forventet atferd. En løsning er å vikle dine forespørsler i tilpasset, asynkron NSOperationunderklasse, og deretter bruke maxConcurrentOperationCountav operasjonen kø for å kontrollere antall samtidige forespørsler i stedet for HTTPMaximumConnectionsPerHostparameteren.

Den opprinnelige AFNetworking gjorde en fantastisk jobb innpakning forespørsler i drift, noe som gjorde dette trivielt. Men AFNetworking sin NSURLSessionimplementering aldri gjorde dette, heller ikke Alamofire.


Du kan enkelt pakke inn Requesti en NSOperationunderklasse. For eksempel:

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

Deretter, når jeg ønsker å starte min 50 forespørsler, ville jeg gjøre noe som dette:

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

På den måten vil de forespørsler bli begrenset av maxConcurrentOperationCount, og vi trenger ikke å bekymre deg for noen av henvendelsene timing ut ..

Dette er et eksempel AsynchronousOperationbasisklasse, som tar vare på KVN forbundet med asynkron / samtidig NSOperationunderklasse:

//
//  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`")
    }
}

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

Det er andre mulige varianter av dette mønsteret, men bare sørge for at du (a) returnerer truefor asynchronous; og (b) du legger den nødvendige isFinishedog isExecutingKVN som skissert Konfigurering Operations for samtidig utførelse delen av Samtidighet Programming Guide: Operasjon køer .

Svarte 19/11/2014 kl. 17:00
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more