patrones patron method diseƱo conexion objective-c multithreading memory-management thread-safety singleton

objective-c - method - patron singleton php



Thread instanciaciĆ³n segura de un singleton (5)

Asegúrese de leer la discusión sobre esta pregunta / respuesta también. ¿Por qué deberíamos separar las llamadas alloc e init para evitar interbloqueos en Objective-C?

Para ampliar el problema de condición de carrera; la verdadera solución es no tener una inicialización indeterminada dentro de su aplicación. La inicialización indeterminada o diferida produce un comportamiento que puede cambiar fácilmente debido a cambios aparentemente inocuos: configuración, cambios de código "no relacionados", etc.

Es mejor inicializar explícitamente los subsistemas en un punto conocido de la vida útil del programa. Ie drop [MyClass sharedInstance]; en el método applicationDidFinishLaunching: su delegado de la applicationDidFinishLaunching: si realmente necesita que el subsistema se inicialice al principio del programa (o lo mueva incluso antes, si quiere estar a la defensiva).

Mejor aún para mover la inicialización de ese método por completo. Es decir [MyClass initializeSharedInstance]; donde +sharedInstance afirma () si ese método no se llama primero.

Por mucho que sea fan de la conveniencia, 25 años de programación de ObjC me han enseñado que la inicialización lenta es una fuente de más dolores de cabeza de mantenimiento y refactorización de lo que vale.

Mientras exista la condición de carrera que se describe a continuación, este código no corrige lo que se describe a continuación. Lo hizo durante un par de décadas cuando no nos preocupamos por la simultaneidad en los inicializadores de instancias compartidas. Dejando el código equivocado para la prosperidad.

Tenga en cuenta que, tanto para las respuestas correctas de Colin como de Harald, existe una condición de raza muy sutil que podría llevarlo a un mundo de ayes.

A saber, si el -init de la clase asignada llama al método sharedInstance , lo hará antes de que se establezca la variable. En ambos casos, conducirá a un punto muerto.

Esta es la única vez que desea separar el alloc y el init. Codificando el código de Colin porque es la mejor solución (asumiendo Mac OS X):

+(MyClass *)sharedInstance { static MyClass *sharedInstance = nil; static dispatch_once_t pred; // partial fix for the "new" concurrency issue if (sharedInstance) return sharedInstance; // partial because it means that +sharedInstance *may* return an un-initialized instance // this is from https://stackoverflow.com/questions/20895214/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/20895427#20895427 dispatch_once(&pred, ^{ sharedInstance = [MyClass alloc]; sharedInstance = [sharedInstance init]; }); return sharedInstance; }

tenga en cuenta que esto solo funciona en Mac OS X; X 10.6+ e iOS 4.0+, en particular. En los sistemas operativos más antiguos, donde los bloques no están disponibles, use un bloqueo o uno de los diversos medios para hacer algo una vez que no esté basado en bloques.

El patrón anterior en realidad no previene el problema descrito en el texto y causará un punto muerto cuando se encuentre. El problema es que dispatch_once() no es reentrante y, por lo tanto, si el init llama a sharedInstance , wedge city .

¿Qué método de sincronización usar para asegurar un singleton sigue siendo un singleton?

+(Foo*)sharedInstance { @synchronized(self) { if (nil == _sharedInstance) { _sharedInstance = [[Foo alloc] init]; ... } } return _sharedInstance; }

o usando un mutex?

#import <pthread.h> static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER; +(Foo*)sharedInstance { pthread_mutex_lock(&_mutex); if (nil == _sharedInstance) { _sharedInstance = [[Foo alloc] init]; ... } pthread_mutex_unlock(&_mutex); return _sharedInstance; }

Hmmm ... ¿Algún comentario sobre esto?



La forma más rápida y segura de hacer esto es con Grand Central Dispatch (libdispatch) y dispatch_once ()

+(MyClass *)sharedInstance { static MyClass *sharedInstance = nil; static dispatch_once_t pred; dispatch_once(&pred, ^{ sharedInstance = [[MyClass alloc] init]; }); return sharedInstance; }


Si a alguien le importa, aquí hay otra macro para la misma cosa :)

En mi humilde opinión, proporciona una mayor flexibilidad en comparación con las otras variaciones .

#define SHARED_INSTANCE(...) ({/ static dispatch_once_t pred;/ static id sharedObject;/ dispatch_once(&pred, ^{/ sharedObject = (__VA_ARGS__);/ });/ sharedObject;/ })

Uso, inicialización de una línea:

+ (instancetype) sharedInstanceOneLine { return SHARED_INSTANCE( [[self alloc] init] ); }

Uso, inicialización multilínea (observe las llaves alrededor del bloque de código):

+ (instancetype) sharedInstanceMultiLine { return SHARED_INSTANCE({ NSLog(@"creating shared instance"); CGFloat someValue = 84 / 2.0f; [[self alloc] initWithSomeValue:someValue]; // no return statement }); }

Uso en la parte correcta de una tarea:

- (void) someMethod { MethodPrivateHelper *helper = SHARED_INSTANCE( [[MethodPrivateHelper alloc] init] ); // do smth with the helper } // someMethod should not call itself to avoid deadlock, see bbum''s answer

Esta modificación utiliza dos características de lenguaje: la extensión de expresiones compuestas GCC, que también es compatible con Clang, y el soporte macros variadas C99.

Después del preprocesamiento, el resultado será similar (puede probarlo usted mismo invocando Product > Perform Action > Preprocess "YourClassName.m" en Xcode 5):

+ (instancetype) sharedInstanceOneLine { return ({ static dispatch_once_t pred; static id sharedObject; dispatch_once(&pred, ^{ sharedObject = ( [[self alloc] init] ); }); sharedObject; // this object will be returned from the block }); } + (instancetype) sharedInstanceMultiLine { return ({ static dispatch_once_t pred; static id sharedObject; dispatch_once(&pred, ^{ sharedObject = ({ NSLog(@"creating shared instance"); CGFloat someValue = 84 / 2.0f; [[self alloc] initWithSomeValue:someValue]; }); }); sharedObject; }); } - (void) someMethod { MethodPrivateHelper *helper = ({ static dispatch_once_t pred; static id sharedObject; dispatch_once(&pred, ^{ sharedObject = ( [[MethodPrivateHelper alloc] init] ); }); sharedObject; }); }


Si a alguien le importa, aquí hay una macro para la misma cosa:

/*! * @function Singleton GCD Macro */ #ifndef SINGLETON_GCD #define SINGLETON_GCD(classname) / / + (classname *)shared##classname { / / static dispatch_once_t pred; / static classname * shared##classname = nil; / dispatch_once( &pred, ^{ / shared##classname = [[self alloc] init]; / }); / return shared##classname; / } #endif