1

Hi basé sur this answer J'ai écrit sous-classe de NSInputStream et cela fonctionne plutôt bien.NSURLSession demande corps passé lentement NSInputStream (gestion de la bande passante)

Maintenant Il s'est avéré que j'ai un scénario où je alimente au serveur une grande quantité de données et pour éviter la famine des autres services, j'ai besoin de contrôler la vitesse d'alimentation des données. Donc, je me suis amélioré functinality de ma sous-classe avec les conditions suivantes:

  • lorsque les données doivent être reportées, hasBytesAvailable renvoie NO et tentatives de lecture se termine par zéro octets lus
  • lorsque les données peuvent être envoyées, - read:maxLength: permet de lire une certaine quantité maximale données à la fois (par défaut 2048).
  • lorsque - read:maxLength: renvoie 0 octets lus, le délai requis est calculé et après ce délai, l'événement NSStreamEventHasBytesAvailable est envoyé.

est ici parties intéressantes de code (il est mélangé avec C++):

- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { 
    if (![self isOpen]) { 
     return kOperationFailedReturnCode; 
    } 
    int delay = 0; 
    NSInteger readCount = (NSInteger)self.cppInputStream->Read(buffer, len, delay); 
    if (readCount<0) { 
     return kOperationFailedReturnCode; 
    } 
    LOGD("Stream") << __PRETTY_FUNCTION__ 
      << " len: " << len 
      << " readCount: "<< readCount 
      << " time: " << (int)(-[openDate timeIntervalSinceNow]*1000) 
      << " delay: " << delay; 

    if (!self.cppInputStream->IsEOF()) { 
     if (delay==0) 
     { 
      [self enqueueEvent: NSStreamEventHasBytesAvailable]; 
     } else { 
      NSTimer *timer = [NSTimer timerWithTimeInterval: delay*0.001 
                target: self 
                selector: @selector(notifyBytesAvailable:) 
                userInfo: nil 
                repeats: NO]; 

      [self enumerateRunLoopsUsingBlock:^(CFRunLoopRef runLoop) { 
       CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes); 
      }]; 
     } 
    } else { 
     [self setStatus: NSStreamStatusAtEnd]; 
     [self enqueueEvent: NSStreamEventEndEncountered]; 
    } 

    return readCount; 
} 

- (void)notifyBytesAvailable: (NSTimer *)timer { 
    LOGD("Stream") << __PRETTY_FUNCTION__ << "notifyBytesAvailable time: " << (int)(-[openDate timeIntervalSinceNow]*1000); 

    [self enqueueEvent: NSStreamEventHasBytesAvailable]; 
} 

- (BOOL)hasBytesAvailable { 
    bool result = self.cppInputStream->HasBytesAvaible(); 
    LOGD("Stream") << __PRETTY_FUNCTION__ << ": " << result << " time: " << (int)(-[openDate timeIntervalSinceNow]*1000); 
    return result; 
} 

j'ai écrit quelques essais pour cela et ça a marché.

Un problème est apparu lorsque j'ai utilisé ce flux avec NSURLSession comme source du corps de la requête HTTP. De journaux, je peux voir que NSURLSession essaie de tout lire à la fois. En première lecture, je renvoie une partie limitée des données. Immédiatement après cela NSURLSession demande s'il y a des octets disponibles (je renvoie NO). Après un certain temps (par exemple 170 ms), j'envoie une notification que les octets sont maintenant disponibles mais NSURLSession ne répond pas à cela et n'appelle aucune méthode de ma classe de flux.

Voici ce que je vois dans les journaux (lors de l'exécution un certain test):

09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper open] 
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 1 time: 0 
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper read:maxLength:] len: 32768 readCount: 2048 time: 0 delay: 170 
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0 
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0 
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0 
09:32:15161[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper notifyBytesAvailable:]notifyBytesAvailable time: 171 

Lorsque le temps est le montant de millisecondes depuis a été ouvert flux.

Looks NSURLSession est incapable de gérer les flux d'entrée avec un débit de données limité. Est-ce que quelqu'un d'autre a eu un problème similaire? Ou a un concept alternatif comment réaliser la gestion de la bande passante sur NSURLSession?

Répondre

1

solutions tha je peux soutenir est:

  1. utilisant NSURLSessionStreamTask, de iOS9 et OSX10.11.
  2. en utilisant ASIHTTPRequest à la place.
+0

'ASIHTTPRequest' est une solution (il supporte la limitation), mais mon projet est loin de se terminer, c'est-à-dire qu'il est déjà trop tard pour changer de framework utilisé. Aussi ce 'ASIHTTPRequest' ressemble à un projet abandonné et la directrice ne l'aime pas. –

0

Malheureusement, NSInputStream est un cluster de classe. Cela rend le sous-classement difficile. Et dans le cas de NSInputStream, toutes les sous-classes sont complètement non supportées et sont susceptibles d'échouer de manière fascinante. (http://blog.bjhomer.com/2011/04/subclassing-nsinputstream.html pour plus de détails.)

Au lieu de sous-classer NSInputStream, vous devez utiliser une paire de flux liés et créer votre propre classe de fournisseur de données pour alimenter les données. Pour ce faire:

  • Appelez CFStreamCreateBoundPair.
  • Convertit l'objet CFReadStream en un pointeur NSInputStream.
  • Convertissez l'objet CFWriteStream en un pointeur NSOutputStream.
  • Passez le flux d'entrée lorsque vous créez la tâche de téléchargement ou l'objet de requête.
  • Créez une classe qui utilise un temporisateur pour transmettre périodiquement le segment de données suivant au flux de sortie.

Si vous faites cela, les données de votre classe de fournisseur de données passe au NSOutputStream seront disponibles pour la lecture de la NSInputStream à l'autre extrémité.

+0

J'ai déjà sous-classe de travail de 'NSOutputStream' (qui possède un flux de socket) qui fonctionne parfaitement SocketRocket et fournit une limitation. La même solution pour 'NSInputStream' ne fonctionne pas pour' NSURLSession'. Si je me souviens bien (la question est assez ancienne) j'ai déjà essayé d'utiliser une paire de flux bornés avec le même résultat. –