2012-06-07 6 views

Respuesta

42

Debo observar primero que esto no es algo bueno que hacer en cualquier situación que no sea la prueba; incluso entonces, proceda con cuidado: AliSoftware proporciona algunos detalles y código de ejemplo en los comentarios a continuación. Consulte también las respuestas interesantes en Can I declare a dispatch_once_t predicate as a member variable instead of static?, incluida información importante from the horse's mouth.

dispatch_once_t es un typedef d long. Su valor falso es 0. Si reinicia ese indicador a 0, dispatch_once() se ejecutará nuevamente. Su problema es "solo" cómo cambiar el valor de una variable estática de otra unidad de compilación. Para esto, creo que es necesario un gancho de prueba de depuración/unidad, así:

MakeWhoopie.h

#import <Foundation/Foundation.h> 

void makeWhoopie(void); 

#ifdef DEBUG 
void resetDispatchOnce(void); 
#endif 

MakeWhoopie.m

#include "MakeWhoopie.h" 

static dispatch_once_t * once_token_debug; 

void makeWhoopie(void) 
{ 

    static dispatch_once_t once_token; 
    once_token_debug = &once_token; // Store address of once_token 
             // to access it in debug function. 
    dispatch_once(&once_token, ^{ 
     NSLog(@"That's what you get, folks."); 
    }); 

    NSLog(@"Making whoopie."); 
} 

#ifdef DEBUG 
void resetDispatchOnce(void) 
{ 
    *once_token_debug = 0; 
} 
#endif 

(También podría mover once_token hasta presentar . nivel y cambiarlo directamente)

intentar esto:

#import <Foundation/Foundation.h> 
#import "MakeWhoopie.h" 

int main(int argc, const char * argv[]) 
{ 

    @autoreleasepool { 

     makeWhoopie(); 
     makeWhoopie(); 
     resetDispatchOnce(); 
     makeWhoopie(); 
    } 
    return 0; 
} 

Resultados: en

2012-06-07 18: 45: 28.134 ResetDispatchOnce [8628: 403] Eso es lo que se obtiene, amigos.
2012-06-07 18: 45: 28.163 ResetDispatchOnce [8628: 403] Haciendo whoopie.
2012-06-07 18: 45: 28.164 ResetDispatchOnce [8628: 403] Haciendo whoopie.
2012-06-07 18: 45: 28.165 ResetDispatchOnce [8628: 403] Eso es lo que obtienes, amigos.
2012-06-07 18: 45: 28.165 ResetDispatchOnce [8628: 403] Haciendo whoopie.

+0

El objetivo de 'dispatch_once' es que es seguro para subprocesos. El problema al configurar '* once_token_debug = 0' de esta manera es que no es seguro para subprocesos en todos los casos en que otro subproceso usa' dispatch_once (& oneToken, ...) 'mientras configura el' onceToken' por usted mismo. ¿Cómo es que podemos evitar tal problema? – AliSoftware

+0

@AliSoftware: El escenario en la pregunta es la prueba unitaria, y ese es el único uso que propongo para este procedimiento. El token se restablece _between_ tests, para crear una lista limpia tal como se crearía entre las ejecuciones del programa más grande. Enhebrar no es un problema porque el programa no se ejecuta entre pruebas. –

+0

Entendí esto, pero todos deben ser conscientes de eso y no estar tentados a usar esto de una manera que no sea segura contra el hilo en otro lugar.Y puede haber algunos casos incluso en Pruebas unitarias que no sean seguros para subprocesos, especialmente si uno codifica mal su prueba y realiza acciones asíncronas que continúan ejecutándose incluso después de que finalizan las pruebas (como una llamada a 'dispatch_after (10s,^{/ * sthg que usa sharedInstance * /} 'si la prueba alcanza su tiempo de espera, fallará y se detendrá y llegará a' tearDown' pero el bloque que usa sharedInstance aún se enviará en un futuro próximo ... después de tearDown. – AliSoftware

4

Nosotros también probamos nuestros singletons y ocasionalmente necesitamos reemplazarlos por objetos simulados o restablecerlos. Tomé la respuesta de Josh y la simplifiqué un poco más:

static ArticleManager *_sharedInstance = nil; 
static dispatch_once_t once_token = 0; 

+(ArticleManager *)sharedInstance { 
    dispatch_once(&once_token, ^{ 
     if (_sharedInstance == nil) { 
      _sharedInstance = [[ArticleManager alloc] init]; 
     } 
    }); 
    return _sharedInstance; 
} 

+(void)setSharedInstance:(ArticleManager *)instance { 
    if (instance == nil) once_token = 0; 
    _sharedInstance = instance; 
} 
+0

Escribí una publicación en el blog explicando por qué debería hacerlo de esta manera: http://twobitlabs.com/2013/01/objective-c-singleton-pattern-unit-testing/ – ToddH

+0

Mencioné poner el token una vez al nivel de archivo en mi respuesta. Tenga en cuenta que el uso de mi código o el de cualquier persona publicado en SO, ya sea textualmente o modificado, [requiere] (http://creativecommons.org/licenses/by-sa/3.0/) que incluya [atribución] (http : //blog.stackoverflow.com/2009/06/attribution-required/). En este caso, el mecanismo es tan directo que no vale la pena mencionarlo, pero no se acostumbre a escribir en un blog basado en las respuestas de SO sin al menos vincularlo. –

+0

En realidad, la verificación == nil dentro de dispatch_once parece redundante. Solo restablece el once_token si _sharedInstance se estableció como nulo. – Vitali

Cuestiones relacionadas