objective-c cocoa-touch cocoa automatic-ref-counting weak-references

objective c - Colecciones de cero referencias débiles bajo ARC



objective-c cocoa-touch (8)

¿Cómo puedo obtener una matriz de referencias débiles de puesta a cero bajo ARC? No quiero que la matriz retenga los objetos. Y me gustaría que los elementos de la matriz se eliminen ellos mismos cuando se desasignan, o establecer esas entradas en cero.

Del mismo modo, ¿cómo puedo hacer eso con un diccionario? No quiero que el diccionario retenga los valores. Y de nuevo, me gustaría que los elementos del diccionario se eliminen ellos mismos cuando se desasignan los valores o establezcan los valores en cero. (Necesito conservar las claves, que son los identificadores únicos, al menos hasta que se desasignan los valores correspondientes).

Estas dos preguntas cubren un terreno similar:

Pero tampoco pide referencias de puesta a cero .

Según la documentación, ni NSPointerArray ni NSHashMap admiten referencias débiles en ARC. El valor de NSValue no retenidoObjectValue tampoco funcionará, ya que no es zeroing.

La única solución que veo es crear mi propia clase de contenedor similar a NSValue con una propiedad (weak) , como esta respuesta menciona, cerca del final . ¿Hay una mejor manera que no estoy viendo?

Estoy desarrollando para OS X 10.7 y iOS 6.0.


Aquí hay un código para una clase de contenedor de referencia débil de puesta a cero. Funciona correctamente con NSArray, NSSet y NSDictionary.

La ventaja de esta solución es que es compatible con sistemas operativos anteriores y así de simple. La desventaja es que al iterar, probablemente necesite verificar que -nonretainedObjectValue no sea nulo antes de usarlo.

Es la misma idea que el envoltorio en la primera parte de la respuesta de Cocoanetics, que usa bloques para lograr lo mismo.

WeakReference.h

@interface WeakReference : NSObject { __weak id nonretainedObjectValue; __unsafe_unretained id originalObjectValue; } + (WeakReference *) weakReferenceWithObject:(id) object; - (id) nonretainedObjectValue; - (void *) originalObjectValue; @end

WeakReference.m

@implementation WeakReference - (id) initWithObject:(id) object { if (self = [super init]) { nonretainedObjectValue = originalObjectValue = object; } return self; } + (WeakReference *) weakReferenceWithObject:(id) object { return [[self alloc] initWithObject:object]; } - (id) nonretainedObjectValue { return nonretainedObjectValue; } - (void *) originalObjectValue { return (__bridge void *) originalObjectValue; } // To work appropriately with NSSet - (BOOL) isEqual:(WeakReference *) object { if (![object isKindOfClass:[WeakReference class]]) return NO; return object.originalObjectValue == self.originalObjectValue; } @end


Las referencias débiles de puesta a cero requieren OS X 10.7 o iOS 5.

Solo puede definir variables débiles en código, ivars o bloques. AFAIK no hay manera de crear dinámicamente (en tiempo de ejecución) una variable débil porque ARC tiene efecto durante el tiempo de compilación. Cuando ejecuta el código, ya tiene las actualizaciones y versiones agregadas para usted.

Habiendo dicho eso, probablemente puedas abusar de bloques para lograr un efecto como este.

Tiene un bloque que simplemente devuelve la referencia.

__weak id weakref = strongref; [weakrefArray addObject:[^{ return weakref; } copy]];

Tenga en cuenta que debe copiar el bloque para copiarlo al montón.

Ahora puede recorrer la matriz cuando lo desee, los objetos desasignados en bloques devolverán nulo. Luego puedes eliminar esos.

No puede hacer que el código se ejecute automáticamente cuando la referencia débil se pone a cero. Si esto es lo que quiere, entonces puede hacer uso de la función de los objetos asociados. Aquellos se desasignan al mismo tiempo que el objeto al que están asociados. Por lo tanto, podría tener su propia etiqueta centinela que informa a la colección débil sobre la desaparición de los objetos.

Tendría un objeto asociado para observar el dealloc (si la asociación es la única referencia) y el objeto asociado tendría un puntero a la observación de la colección. Luego, en el centinela dealloc, llama a la colección débil para informarle que el objeto observado se ha ido.

Aquí está mi descripción sobre objetos asociados: http://www.cocoanetics.com/2012/06/associated-objects/

Aquí está mi implementación:

---- DTWeakCollection.h @interface DTWeakCollection : NSObject - (void)checkInObject:(id)object; - (NSSet *)allObjects; @end ---- DTWeakCollection.m #import "DTWeakCollection.h" #import "DTWeakCollectionSentry.h" #import <objc/runtime.h> static char DTWeakCollectionSentryKey; @implementation DTWeakCollection { NSMutableSet *_entries; } - (id)init { self = [super init]; if (self) { _entries = [NSMutableSet set]; } return self; } - (void)checkInObject:(id)object { NSUInteger hash = (NSUInteger)object; // make weak reference NSNumber *value = [NSNumber numberWithUnsignedInteger:hash]; [_entries addObject:value]; // make sentry DTWeakCollectionSentry *sentry = [[DTWeakCollectionSentry alloc] initWithWeakCollection:self forObjectWithHash:hash]; objc_setAssociatedObject(object, &DTWeakCollectionSentryKey, sentry, OBJC_ASSOCIATION_RETAIN); } - (void)checkOutObjectWithHash:(NSUInteger)hash { NSNumber *value = [NSNumber numberWithUnsignedInteger:hash]; [_entries removeObject:value]; } - (NSSet *)allObjects { NSMutableSet *tmpSet = [NSMutableSet set]; for (NSNumber *oneHash in _entries) { // hash is actually a pointer to the object id object = (__bridge id)(void *)[oneHash unsignedIntegerValue]; [tmpSet addObject:object]; } return [tmpSet copy]; } @end ---- DTWeakCollectionSentry.h #import <Foundation/Foundation.h> @class DTWeakCollection; @interface DTWeakCollectionSentry : NSObject - (id)initWithWeakCollection:(DTWeakCollection *)weakCollection forObjectWithHash:(NSUInteger)hash; @end --- DTWeakCollectionSentry.m #import "DTWeakCollectionSentry.h" #import "DTWeakCollection.h" @interface DTWeakCollection (private) - (void)checkOutObjectWithHash:(NSUInteger)hash; @end @implementation DTWeakCollectionSentry { __weak DTWeakCollection *_weakCollection; NSUInteger _hash; } - (id)initWithWeakCollection:(DTWeakCollection *)weakCollection forObjectWithHash:(NSUInteger)hash { self = [super init]; if (self) { _weakCollection = weakCollection; _hash = hash; } return self; } - (void)dealloc { [_weakCollection checkOutObjectWithHash:_hash]; } @end

Esto se usaría así:

NSString *string = @"bla"; @autoreleasepool { _weakCollection = [[DTWeakCollection alloc] init]; [_weakCollection checkInObject:string]; __object = [NSNumber numberWithInteger:1123333]; [_weakCollection checkInObject:__object]; }

si saca todos los Objetos dentro del bloque de reserva automática, entonces tiene dos objetos allí. Afuera solo tienes la cuerda.

Descubrí que en el desglose de la entrada, la referencia del objeto ya es nula, por lo que no puede usar __weak. En cambio, estoy usando la dirección de memoria del objeto como hash. Si bien estos todavía están en _entries, puede tratarlos como un objeto real y allObjects devuelve una matriz autorrellenada de referencias sólidas.

Nota: esto no es seguro para subprocesos. Si se trata de dealloc en colas / subprocesos no principales, deberá tener cuidado de sincronizar el acceso y la mutación del conjunto interno de entradas.

Nota 2: Actualmente, esto solo funciona con la comprobación de objetos en una única colección débil, ya que un segundo check in sobrescribiría al centinela asociado. Si necesita esto con múltiples colecciones débiles, el centinela debería tener una matriz de esas colecciones.

Nota 3: Cambié la referencia del centinela a la colección a débil también para evitar un ciclo de retención.

Nota 4: Aquí hay un typedef y funciones auxiliares que manejan la sintaxis del bloque por usted:

typedef id (^WeakReference)(void); WeakReference MakeWeakReference (id object) { __weak id weakref = object; return [^{ return weakref; } copy]; } id WeakReferenceNonretainedObjectValue (WeakReference ref) { if (ref == nil) return nil; else return ref (); }


Si está trabajando con al menos MacOS X 10.5 o iOS6, entonces:

  • NSPointerArray weakObjectsPointerArray / pointerArrayWithWeakObjects es una referencia de referencia débil para NSArray
  • NSHashTable hashTableWithWeakObjects / weakObjectsHashTable es una referencia de referencia débil para NSSet
  • NSMapTable es una plataforma de referencia débil para NSDictionary (puede tener claves débiles y / o valores débiles)

Tenga en cuenta que las colecciones pueden no advertir inmediatamente que los objetos se han ido, por lo que los recuentos podrían ser aún mayores, y las claves podrían existir aunque el objeto asociado haya desaparecido, etc. NSPointerArray tiene un método de comparación que en teoría debería eliminar cualquier punteros nilled. Los documentos NSMapTable observan que las claves para los mapas weakToStrong permanecerán en la tabla (aunque sean efectivamente nulas) hasta que se redimensionen, lo que significa que los punteros fuertes del objeto pueden permanecer en la memoria incluso si ya no se hace referencia lógica.

Editar: Veo el cartel original sobre ARC. Creo que efectivamente fue 10.8 e iOS 6 antes de que esos contenedores se pudieran usar con ARC. Creo que las cosas "débiles" anteriores fueron para GC. ARC no fue soportado hasta 10.7, por lo que realmente es una pregunta si necesita soportar esa versión y no la 10.6, en cuyo caso necesitaría hacer la suya (o tal vez usar funciones personalizadas con NSPointerFunctions, que a su vez puede usarse con NSPointerArray, NSHashTable y NSMapTable).


Simplemente agregue una categoría para NSMutableSet con el siguiente código:

@interface WeakReferenceObj : NSObject @property (nonatomic, weak) id weakRef; @end @implementation WeakReferenceObj + (id)weakReferenceWithObj:(id)obj{ WeakReferenceObj *weakObj = [[WeakReferenceObj alloc] init]; weakObj.weakRef = obj; return weakObj; } @end @implementation NSMutableSet(WeakReferenceObj) - (void)removeDeallocRef{ NSMutableSet *deallocSet = nil; for (WeakReferenceObj *weakRefObj in self) { if (!weakRefObj.weakRef) { if (!deallocSet) { deallocSet = [NSMutableSet set]; } [deallocSet addObject:weakRefObj]; } } if (deallocSet) { [self minusSet:deallocSet]; } } - (void)addWeakReference:(id)obj{ [self removeDeallocRef]; [self addObject:[WeakReferenceObj weakReferenceWithObj:obj]]; } @end

La misma manera de crear una categoría para NSMutableArray y NSMutableDictionary.

Eliminar la referencia dealloc en didReceiveMemoryWarning será mejor.

- (void)didReceiveMemoryWarning{ [yourWeakReferenceSet removeDeallocRef]; }

Entonces, lo que debe hacer es invocar addWeakReference: para su clase de contenedor.


Solo creo una versión de ref débil no resistente a los hilos de NSMutableDictionary y NSMutableSet. Codifique aquí: https://gist.github.com/4492283

Para NSMutableArray, las cosas son más complicadas porque no pueden contener nada y un objeto puede agregarse a la matriz varias veces. Pero es factible implementar uno.


Vea la clase BMNullableArray , que es parte de mi framework BMCommons para una solución completa a este problema.

Esta clase permite insertar objetos nulos y tiene la opción de hacer referencia débilmente a los objetos que contiene (nillándolos automáticamente cuando se desasignan).

El problema con la eliminación automática (que traté de implementar) es que obtienes problemas de seguridad de subprocesos, ya que no está garantizado en qué momento se desasignarán los objetos, lo que podría ocurrir al iterar la matriz.

Esta clase es una mejora sobre NSPointerArray, ya que abstrae algunos detalles de nivel inferior y le permite trabajar con objetos en lugar de punteros. Incluso es compatible con NSFastEnumeration para iterar sobre la matriz con referencias nulas allí.


NSMapTable debería funcionar para usted. Disponible en iOS 6.


@interface Car : NSObject @end @implementation Car -(void) dealloc { NSLog(@"deallocing"); } @end int main(int argc, char *argv[]) { @autoreleasepool { Car *car = [Car new]; NSUInteger capacity = 10; id __weak *_objs = (id __weak *)calloc(capacity,sizeof(*_objs)); _objs[0] = car; car = nil; NSLog(@"%p",_objs[0]); return EXIT_SUCCESS; } }

Salida:

2013-01-08 10:00:19.171 X[6515:c07] deallocing 2013-01-08 10:00:19.172 X[6515:c07] 0x0

editar : Creé una colección de mapas débiles de muestra desde cero en base a esta idea. Funciona, pero es feo por varias razones:

Usé una categoría en NSObject para agregar @properties para la clave, el siguiente cubo del mapa y una referencia a la colección que posee el objeto.

Una vez que anulas el objeto, desaparece de la colección.

PERO, para que el mapa tenga una capacidad dinámica, necesita recibir una actualización de la cantidad de elementos para calcular el factor de carga y expandir la capacidad si es necesario. Es decir, a menos que desee realizar una actualización Θ (n) iterando toda la matriz cada vez que agrega un elemento. Hice esto con una devolución de llamada en el método dealloc del objeto de muestra que estoy agregando a la colección. Podría editar el objeto original (lo cual hice por brevedad) o heredar de una superclase, o swizzle el dealloc. En cualquier caso, feo.

Sin embargo, si no le importa tener una colección de capacidad fija, no necesita las devoluciones de llamada. La colección utiliza un encadenamiento separado y suponiendo una distribución uniforme de la función hash, el rendimiento será Θ (1 + n / m) siendo n = elementos, m = capacidad. Pero (más peros) para evitar romper el encadenamiento necesitaría agregar un enlace anterior como una propiedad de categoría @ y vincularlo al siguiente elemento en el desglose del elemento. Y una vez que tocamos el dealloc, es igualmente bueno notificar a la colección que el elemento se está eliminando (que es lo que está haciendo ahora).

Finalmente, tenga en cuenta que la prueba en el proyecto es mínima y que podría haber pasado por alto algo.