Tengo un montón de repetición de código en mi clase que tiene el siguiente aspecto:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                              delegate:self];

El problema con peticiones asíncronas es cuando usted tiene varias solicitudes de entrar, y tiene un delegado asignado para el tratamiento de todos ellos como una sola entidad, un montón de ramificación y feo código comienza a formular va:

Qué tipo de datos estamos recibiendo de vuelta? Si contiene esta, hacen que, más que otros. Sería útil que creo que para ser capaz de etiquetar estas peticiones asíncronas, como eres capaz de etiqueta de puntos de vista con los Identificadores.

Estaba curioso por saber qué estrategia es la más eficiente para la gestión de una clase que maneja múltiples peticiones asíncronas.

InformationsquelleAutor Coocoo4Cocoa | 2008-12-01

13 Comentarios

  1. 77

    Puedo hacer un seguimiento de las respuestas en un CFMutableDictionaryRef con llave por la NSURLConnection asociados con él. es decir:

    connectionToInfoMapping =
        CFDictionaryCreateMutable(
            kCFAllocatorDefault,
            0,
            &kCFTypeDictionaryKeyCallBacks,
            &kCFTypeDictionaryValueCallBacks);

    Puede parecer extraño usar este en lugar de NSMutableDictionary pero lo hago porque este CFDictionary sólo conserva sus claves (el NSURLConnection), mientras que NSDictionary copias de sus llaves (y NSURLConnection no admite la copia).

    Una vez hecho esto:

    CFDictionaryAddValue(
        connectionToInfoMapping,
        connection,
        [NSMutableDictionary
            dictionaryWithObject:[NSMutableData data]
            forKey:@"receivedData"]);

    y ahora tengo un «info» en el diccionario de datos para cada conexión que puede utilizar para rastrear información sobre la conexión y la «info» diccionario ya contiene datos mutables objeto de que pueda utilizar para almacenar la respuesta de datos como viene en.

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        NSMutableDictionary *connectionInfo =
            CFDictionaryGetValue(connectionToInfoMapping, connection);
        [[connectionInfo objectForKey:@"receivedData"] appendData:data];
    }
    • Ya que es posible que dos o más conexiones asincrónicas puede entrar a los métodos de delegado en un momento, hay algo específico que uno necesita hacer para asegurar un correcto comportamiento?
    • (He de crear una nueva pregunta aquí preguntando esto: stackoverflow.com/questions/1192294/… )
    • Este no es el hilo seguro si el delegado se llama desde varios subprocesos. Usted debe utilizar la exclusión mutua bloqueos para proteger las estructuras de datos. Una mejor solución es crear subclases de NSURLConnection y la adición de respuesta y referencias de datos como variables de instancia. Me estoy dando una respuesta más detallada explicando esto en Nocturne la pregunta: stackoverflow.com/questions/1192294/…
    • Como se dijo antes, esto no es seguro para subprocesos. Prefiero prefieren resolver este problema mediante el uso de NSOperation subclases y NSOperationQueue, que en addion le permite administrar el número de las continuas peticiones a la vez. Cada una de estas operaciones puede manejar sus NSURLConnection devoluciones de llamada e informar a sus delegados individuales sobre los avances y resultados.
    • Aldi… es es hilo segura, siempre comienza todas las conexiones desde el mismo hilo (que usted puede hacer fácilmente mediante la invocación de su inicio de conexión utilizando el método performSelector:onThread:withObject:waitUntilDone:). Poner todas las conexiones en un NSOperationQueue tiene diferentes problemas si intenta iniciar más conexiones de las que max operaciones simultáneas de la cola (operaciones de obtener en la cola en lugar de ejecutar simultáneamente). NSOperationQueue funciona bien para la CPU operaciones, pero para la red de operaciones limitadas, es mejor el uso de un enfoque que no utiliza un tamaño fijo de grupo de subprocesos.
    • Esto me ayudó un montón. Gracias Matt!
    • Esto es realmente útil, gracias! ¿Cómo puedo deshacerme de esta advertencia? «advertencia: pasar argumento 1 de ‘CFDictionaryAddValue’ descartes clasificados de puntero de tipo de destino»
    • Esto realmente ayudó así! Y @josh brown , una de las razones que usted puede ser que consiga esta advertencia es que si usted está utilizando CFMutableDictionaryRef como un puntero… me refiero a CFMutableDictionaryRef *dic = CFDictionaryCreateMutable(... en lugar de sólo CFMutableDictionaryRef dic = CFDictionaryCreateMutable(...
    • Realmente funcionó para mí, Gracias.
    • Otro método para hacer esto se muestra a continuación: github.com/dbowen/Demiurgic-JSON-RPC. @Matt Gallagher, ¿cuáles son los pros y los contras de este método frente a su propia solución?
    • Sólo quería compartir que para iOS 6.0 y superiores, se puede utilizar un [NSMapTable weakToStrongObjectsMapTable] en lugar de un CFMutableDictionaryRef y guardar la molestia. Funcionó bien para mí.

  2. 19

    Tengo un proyecto donde tengo dos distintas NSURLConnections, y quería usar el mismo delegado. Yo lo que hice fue crear dos propiedades en mi clase, uno para cada conexión. Luego en el método de delegado, puedo comprobar si la conexión es

    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        if (connection == self.savingConnection) {
            [self.savingReturnedData appendData:data];
        }
        else {
            [self.sharingReturnedData appendData:data];
        }
    }

    Esto también me permite cancelar una conexión específica por su nombre cuando sea necesario.

    • Esta es una solución mucho más sencilla y a mi me funciono perfectamente!
    • tenga cuidado de esto es problemático, ya que se tienen las condiciones de carrera
    • ¿Cómo asignar los nombres (savingConnection y sharingReturnedData) para cada conexión en el primer lugar?
    • no, no hay ninguna carrera de condición inherente a este código. Tendrías que ir muy lejos, fuera de su camino con la creación de la conexión de código para crear una condición de carrera
    • su «solución» es exactamente lo que la pregunta original en la que se busca evitar, citando desde arriba: ‘… un montón de ramificación y feo código comienza a formular …’
    • ¿Por qué esto nos lleva a una condición de carrera? Es un concepto nuevo para mí.
    • Soy nuevo en iOS y Objective-C. he intentado utilizar este tipo de enfoque para mi NSURLConnections y no funciona cuando se utiliza en if-else

  3. 16

    Subclases NSURLConnection para almacenar los datos es limpio, menos código que algunas de las otras respuestas, es más flexible, y requiere menos pensado acerca de referencia de gestión.

    //DataURLConnection.h
    #import <Foundation/Foundation.h>
    @interface DataURLConnection : NSURLConnection
    @property(nonatomic, strong) NSMutableData *data;
    @end
    
    //DataURLConnection.m
    #import "DataURLConnection.h"
    @implementation DataURLConnection
    @synthesize data;
    @end

    Usarlo como si fuera NSURLConnection y se acumulan los datos en su propiedad de datos:

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        ((DataURLConnection *)connection).data = [[NSMutableData alloc] init];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [((DataURLConnection *)connection).data appendData:data];
    }

    Que es.

    Si quieres ir más lejos, se puede añadir un bloque para servir como una devolución de llamada con sólo un par de líneas de código:

    //Add to DataURLConnection.h/.m
    @property(nonatomic, copy) void (^onComplete)();

    Conjunto como este:

    DataURLConnection *con = [[DataURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    con.onComplete = ^{
        [self myMethod:con];
    };
    [con start];

    y invocarlo cuando la carga está terminada como este:

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        ((DataURLConnection *)connection).onComplete();
    }

    Puede ampliar el bloque para aceptar los parámetros o simplemente pasar el DataURLConnection como un argumento para el método de las necesidades dentro de la no-args bloque como se muestra

    • Esta es una fantástica respuesta que funcionó muy bien para mi caso. Muy sencillo y limpio!
  4. 8

    ESTO NO ES UNA NUEVA RESPUESTA. POR FAVOR, DÉJAME MOSTRARTE CÓMO ME HIZO

    Para distinguir los diferentes NSURLConnection dentro de la misma clase los métodos de delegado, yo uso NSMutableDictionary, para establecer y quitar el NSURLConnection, utilizando su (NSString *)description como clave.

    El objeto que he elegido para setObject:forKey es la única dirección URL que se utiliza para iniciar la NSURLRequest, el NSURLConnection usos.

    Una vez establecido NSURLConnection se evalúa en

    -(void)connectionDidFinishLoading:(NSURLConnection *)connection, it can be removed from the dictionary.
    
    //This variable must be able to be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    NSMutableDictionary *connDictGET = [[NSMutableDictionary alloc] init];
    //...//
    
    //You can use any object that can be referenced from - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    [connDictGET setObject:anyObjectThatCanBeReferencedFrom forKey:[aConnectionInstanceJustInitiated description]];
    //...//
    
    //At the delegate method, evaluate if the passed connection is the specific one which needs to be handled differently
    if ([[connDictGET objectForKey:[connection description]] isEqual:anyObjectThatCanBeReferencedFrom]) {
    //Do specific work for connection //
    
    }
    //...//
    
    //When the connection is no longer needed, use (NSString *)description as key to remove object
    [connDictGET removeObjectForKey:[connection description]];
  5. 5

    Un método que he tomado es no utilizar el mismo objeto que el delegado para cada conexión. En su lugar, puedo crear una nueva instancia de mi análisis de clase para cada conexión que se disparó y establecer el delegado a esa instancia.

    • Mucho mejor encapsulación con respecto a una conexión.
  6. 4

    Probar mi clase personalizada, MultipleDownload, que se encarga de todas estas para usted.

    • Esta es una gran cosa!
    • en iOS6 no puede utilizar el NSURLConnection como la clave.
  7. 2

    Por lo general crear una matriz de diccionarios. Cada diccionario tiene un poco de información de identificación, un NSMutableData objeto para almacenar la respuesta, y la conexión en sí. Cuando una conexión delegado método de incendios, miro la conexión del diccionario y manejar en consecuencia.

    • Ben, sería bueno para pedir un fragmento de código de ejemplo? Estoy tratando de imaginar cómo lo estás haciendo, pero no es todo lo que hay.
    • En particular, Ben, ¿cómo buscar palabras en el diccionario? Usted no puede tener un diccionario de diccionarios desde NSURLConnection no implementar NSCopying (por lo que no puede ser utilizado como una clave).
    • Matt tiene una solución excelente a continuación, utilizando CFMutableDictionary, pero yo uso una matriz de diccionarios. Una búsqueda requiere de una iteración. No es el más eficiente, pero es lo suficientemente rápido.
  8. 2

    Una opción es sólo para la subclase NSURLConnection ti mismo y agregar una etiqueta o método similar. El diseño de NSURLConnection es intencionalmente muy bare bones, así que esto es perfectamente aceptable.

    O tal vez usted podría crear un MyURLConnectionController clase que es responsable de la creación y la recolección de una conexión de datos. Entonces sólo tiene que informar a su controlador principal del objeto una vez que la carga está terminada.

  9. 2

    en iOS5 y por encima de usted sólo puede usar el método de clase
    sendAsynchronousRequest:queue:completionHandler:

    No hay necesidad de mantener un seguimiento de las conexiones ya que la respuesta se devuelve en el controlador de finalización.

  10. 1

    Me gusta ASIHTTPRequest.

    • Me gusta mucho el ‘bloques’ de la implementación en ASIHTTPRequest – es como la Anónima Interior de Tipos en Java. Esto supera todas las otras soluciones en términos de código de limpieza y organización.
  11. 1

    Como se ha señalado por otras respuestas, se debe almacenar connectionInfo en algún lugar y buscar en ellos por conexión.

    La más natural de tipo de datos para esto es NSMutableDictionary, pero no puede aceptar NSURLConnection como claves como las conexiones no son copiable.

    Otra opción de utilizar NSURLConnections como claves en NSMutableDictionary es el uso de NSValue valueWithNonretainedObject]:

    NSMutableDictionary* dict = [NSMutableDictionary dictionary];
    NSValue *key = [NSValue valueWithNonretainedObject:aConnection]
    /* store: */
    [dict setObject:connInfo forKey:key];
    /* lookup: */
    [dict objectForKey:key];
  12. 0

    Me decidí a crear una subclase de NSURLConnection y agregar una etiqueta, delegado, y un NSMutabaleData. Tengo un DataController de la clase que se encarga de toda la gestión de datos, incluyendo las solicitudes. He creado un DataControllerDelegate de protocolo, por lo que las opiniones de cada uno/objetos puede escuchar la DataController para saber cuando sus peticiones fueron terminados, y si es necesario lo mucho que se ha descargado o errores. El DataController clase puede utilizar el NSURLConnection subclase para iniciar una nueva solicitud, y guardar el delegado que quiere escuchar la DataController saber cuando la solicitud ha terminado. Esta es mi solución de trabajo en XCode 4.5.2 y ios 6.

    La DataController.h archivo que declara la DataControllerDelegate protocolo). El DataController es también un singleton:

    @interface DataController : NSObject
    
    @property (strong, nonatomic)NSManagedObjectContext *context;
    @property (strong, nonatomic)NSString *accessToken;
    
    +(DataController *)sharedDataController;
    
    -(void)generateAccessTokenWith:(NSString *)email password:(NSString *)password delegate:(id)delegate;
    
    @end
    
    @protocol DataControllerDelegate <NSObject>
    
    -(void)dataFailedtoLoadWithMessage:(NSString *)message;
    -(void)dataFinishedLoading;
    
    @end

    Los métodos clave en la DataController.m de archivo:

    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
        NSLog(@"DidReceiveResponse from %@", customConnection.tag);
        [[customConnection receivedData] setLength:0];
    }
    
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
        NSLog(@"DidReceiveData from %@", customConnection.tag);
        [customConnection.receivedData appendData:data];
    
    }
    
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection {
        NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
        NSLog(@"connectionDidFinishLoading from %@", customConnection.tag);
        NSLog(@"Data: %@", customConnection.receivedData);
        [customConnection.dataDelegate dataFinishedLoading];
    }
    
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        NSURLConnectionWithDelegate *customConnection = (NSURLConnectionWithDelegate *)connection;
        NSLog(@"DidFailWithError with %@", customConnection.tag);
        NSLog(@"Error: %@", [error localizedDescription]);
        [customConnection.dataDelegate dataFailedtoLoadWithMessage:[error localizedDescription]];
    }

    Y para iniciar una petición: [[NSURLConnectionWithDelegate alloc] initWithRequest:request delegate:self startImmediately:YES tag:@"Login" dataDelegate:delegate];

    La NSURLConnectionWithDelegate.h:
    @protocolo DataControllerDelegate;

    @interface NSURLConnectionWithDelegate : NSURLConnection
    
    @property (strong, nonatomic) NSString *tag;
    @property id <DataControllerDelegate> dataDelegate;
    @property (strong, nonatomic) NSMutableData *receivedData;
    
    -(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString *)tag dataDelegate:(id)dataDelegate;
    
    @end

    Y la NSURLConnectionWithDelegate.m:

    #import "NSURLConnectionWithDelegate.h"
    
    @implementation NSURLConnectionWithDelegate
    
    -(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString *)tag dataDelegate:(id)dataDelegate {
        self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];
        if (self) {
            self.tag = tag;
            self.dataDelegate = dataDelegate;
            self.receivedData = [[NSMutableData alloc] init];
        }
        return self;
    }
    
    @end
  13. 0

    Cada NSURLConnection tiene un atributo hash, puede discriminar a todos por este atributo.

    Por ejemplo tengo que mantener cierta información antes y después de la conexión, así que mi RequestManager tener un NSMutableDictionary para ello.

    Un Ejemplo:

    //Make Request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLConnection *c = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
    //Append Stuffs 
    NSMutableDictionary *myStuff = [[NSMutableDictionary alloc] init];
    [myStuff setObject:@"obj" forKey:@"key"];
    NSNumber *connectionKey = [NSNumber numberWithInt:c.hash];
    
    [connectionDatas setObject:myStuff forKey:connectionKey];
    
    [c start];

    Después de la solicitud:

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        NSLog(@"Received %d bytes of data",[responseData length]);
    
        NSNumber *connectionKey = [NSNumber numberWithInt:connection.hash];
    
        NSMutableDictionary *myStuff = [[connectionDatas objectForKey:connectionKey]mutableCopy];
        [connectionDatas removeObjectForKey:connectionKey];
    }

Dejar respuesta

Please enter your comment!
Please enter your name here