25 votos

¿Cómo uso transparente ReactiveCocoa para autenticar antes de hacer llamadas a la API?

Estoy usando ReactiveCocoa en una aplicación que hace que las llamadas remotas web Api. Pero antes de que cualquier cosa puede ser recuperada a partir de un determinado host API, la aplicación debe proporcionar las credenciales del usuario y recuperar una API token, el cual es utilizado para firmar las solicitudes posteriores.

Quiero abstraen este proceso de autenticación, de modo que se produce automáticamente cada vez que hago una llamada a la API. Asumir que tengo una API de cliente de clase que contiene las credenciales del usuario.

// getThing returns RACSignal yielding the data returned by GET /thing.
// if the apiClient instance doesn't already have a token, it must
// retrieve one before calling GET /thing 
RAC(self.thing) = [apiClient getThing]; 

¿Cómo puedo utilizar ReactiveCocoa de forma transparente a causa de la primera (y sólo la primera) solicitar a una API para recuperar y, como efecto secundario, almacenar de forma segura una API token antes de que las solicitudes posteriores se hacen?

También es un requisito que puedo usar combineLatest: (o similar) para inaugurar varias peticiones simultáneas y que todas implícitamente espere a que el token de ser recuperada.

RAC(self.tupleOfThisAndThat) = [RACSignal combineLatest:@[ [apiClient getThis], [apiClient getThat]]];

Además, si la recupera-solicitud de token ya está en vuelo, cuando una llamada a la API está hecho, que la llamada a la API debe esperar hasta que la recupera-solicitud de token se ha completado.

Mi solución parcial de la siguiente manera:

El patrón básico se va a utilizar flattenMap: a un mapa de la señal que genera el token para una señal de que, dado el token, lleva a cabo la deseada solicitud y produce el resultado de la llamada a la API.

Suponiendo que algunos conveniente extensiones NSURLRequest:

- (RACSignal *)requestSignalWithURLRequest:(NSURLRequest *)urlRequest {
    if ([urlRequest isSignedWithAToken])
        return [self performURLRequest:urlRequest];

    return [[self getToken] flattenMap:^ RACSignal * (id token) {
        NSURLRequest *signedRequest = [urlRequest signedRequestWithToken:token];
        assert([urlRequest isSignedWithAToken]);
        return [self requestSignalWithURLRequest:signedRequest];
    }
}

Ahora considere la posibilidad de la suscripción de la implementación de -getToken.

  • En el caso trivial, cuando la ficha ya ha sido recuperada, la suscripción de los rendimientos de los token de inmediato.
  • Si el token no se ha recuperado, la suscripción se pospone a una autenticación de llamada a la API que devuelve el token.
  • Si la autenticación de llamada a la API está en vuelo, se puede agregar otro observador sin causar la autenticación de llamada a la API para ser repetido sobre el alambre.

Sin embargo no estoy seguro de cómo hacer esto. También, cómo y dónde almacenar de forma segura el token? Algún tipo de persistente/repetible de la señal?

42voto

Justin Spahr-Summers Puntos 12167

Así, hay dos cosas importantes que sucede aquí:

  1. Te queremos compartir algunos de los efectos secundarios (en este caso, la recuperación de un símbolo (token) sin re-activación de ellos cada vez que hay un nuevo suscriptor.
  2. Usted quiere que nadie la suscripción a -getToken para obtener los mismos valores, no importa qué.

Con el fin de compartir los efectos secundarios (#1 arriba), vamos a utilizar RACMulticastConnection. Como la documentación dice:

Una conexión de multidifusión encapsula la idea de compartir una suscripción a una señal a muchos suscriptores. Esto es más a menudo se necesita si la suscripción a la base de la señal implica efectos secundarios o no debería ser llamado más de una vez.

Vamos a añadir uno de esos como la propiedad privada en el API de la clase cliente:

@interface APIClient ()
@property (nonatomic, strong, readonly) RACMulticastConnection *tokenConnection;
@end

Ahora, esto va a resolver el caso de N actual de suscriptores que todas tienen el mismo resultado futuro (llamadas a la API de espera en el token de solicitud está en vuelo), pero todavía necesitamos algo más para asegurarse de que los futuros suscriptores de obtener el mismo resultado (el ya recuperado token), no importa cuando se suscriben.

Esto es lo que RACReplaySubject es para:

Una repetición sujeto guarda los valores que se envía (hasta su definida de capacidad) y reenvía aquellos a los nuevos suscriptores. También reproducción de un error o de finalización.

Para atar estos dos conceptos juntos, podemos utilizar RACSignal-multicast: método, que convierte una señal normales en una conexión mediante el uso de un tipo específico de sujeto.

Que podemos usar en la mayoría de los comportamientos en el tiempo de inicialización:

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    // Defer the invocation of -reallyGetToken until it's actually needed.
    // The -defer: is only necessary if -reallyGetToken might kick off
    // a request immediately.
    RACSignal *deferredToken = [RACSignal defer:^{
        return [self reallyGetToken];
    }]

    // Create a connection which only kicks off -reallyGetToken when
    // -connect is invoked, shares the result with all subscribers, and
    // pushes all results to a replay subject (so new subscribers get the
    // retrieved value too).
    _tokenConnection = [deferredToken multicast:[RACReplaySubject subject]];

    return self;
}

Entonces, implementamos -getToken para activar la captura perezosamente:

- (RACSignal *)getToken {
    // Performs the actual fetch if it hasn't started yet.
    [self.tokenConnection connect];

    return self.tokenConnection.signal;
}

Después, cualquier cosa que se suscribe el resultado de -getToken (como -requestSignalWithURLRequest:) se obtiene el token, si no se ha recuperado aún, empezar a ir a buscar si es necesario, o esperar para un vuelo solicitud si es que hay uno.

3voto

Tony Puntos 3724

Qué tal

...

@property (nonatomic, strong) RACSignal *getToken;

...

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    self.getToken = [[RACSignal defer:^{
        return [self reallyGetToken];
    }] replayLazily];
    return self;
}

Sin duda, esta solución es funcional idéntica a la respuesta de Justin arriba. Básicamente tomamos ventaja del hecho de que ya existe método de conveniencia en RACSignal de API pública :)

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X