NSArray de referencias débiles (__unsafe_unretained) a objetos bajo ARC

Necesito almacenar referencias débiles a objetos en un NSArray, para evitar ciclos de retención. No estoy seguro de la syntax adecuada para usar. ¿Es esta la manera correcta?

Foo* foo1 = [[Foo alloc] init]; Foo* foo2 = [[Foo alloc] init]; __unsafe_unretained Foo* weakFoo1 = foo1; __unsafe_unretained Foo* weakFoo2 = foo2; NSArray* someArray = [NSArray arrayWithObjects:weakFoo1, weakFoo2, nil]; 

Tenga en cuenta que tengo que admitir iOS 4.x , por lo tanto, __unsafe_unretained lugar de __weak .


EDITAR (2015-02-18):

Para aquellos que quieran usar verdaderos punteros __weak (no __unsafe_unretained ), por favor revisen esta pregunta en su lugar: Colecciones de cero referencias débiles bajo ARC

Como dijo Jason, no puedes hacer que NSArray almacene referencias débiles. La forma más fácil de implementar la sugerencia de Emile de envolver un objeto dentro de otro objeto que almacena una referencia débil es la siguiente:

 NSValue *value = [NSValue valueWithNonretainedObject:myObj]; [array addObject:value]; 

Otra opción: una categoría que hace que NSMutableArray almacene referencias débiles.

Tenga en cuenta que se trata de referencias “inseguras no retenidas”, no de referencias débiles de puesta a cero automática. Si la matriz sigue activa después de que los objetos se desasignan, tendrá un montón de punteros basura.

Las soluciones para usar un NSValue helper o para crear un objeto collection (array, set, dict) y desactivar sus callbacks Retain / Release no son soluciones 100% a prueba de fallas con respecto al uso de ARC.

Como señalan varios comentarios a estas sugerencias, tales referencias a objetos no funcionarán como verdaderos refs débiles:

Una propiedad débil “adecuada”, según lo admite ARC, tiene dos comportamientos:

  1. No contiene una fuerte referencia al objeto objective. Eso significa que si el objeto no tiene referencias fuertes que lo apunten, el objeto será desasignado.
  2. Si el objeto ref-d es desasignado, la referencia débil se volverá nula.

Ahora, aunque las soluciones anteriores cumplirán con el comportamiento n. ° 1, no exhiben n. ° 2.

Para obtener el comportamiento # 2 también, debes declarar tu propia clase de ayuda. Tiene solo una propiedad débil para mantener su referencia. A continuación, agrega este objeto auxiliar a la colección.

Ah, y una cosa más: iOS6 y OSX 10.8 supuestamente ofrecen una mejor solución:

 [NSHashTable weakObjectsHashTable] [NSPointerArray weakObjectsPointerArray] [NSPointerArray pointerArrayWithOptions:] 

Estos deberían darle contenedores que contienen referencias débiles (pero tenga en cuenta los comentarios de Matt a continuación).

Soy nuevo en el objective-C, después de 20 años de escribir c ++.

En mi opinión, Objective-C es excelente para mensajería débilmente acoplada, pero horrible para la administración de datos.

¡Imagínese lo feliz que me resulta descubrir que xcode 4.3 es compatible con Objective-C ++!

Así que ahora cambio el nombre de todos mis archivos .m a .mm (comstack como objective-c ++) y uso contenedores estándar de c ++ para la gestión de datos.

Por lo tanto, el problema de la “matriz de punteros débiles” se convierte en un estándar :: de punteros de objetos __weak:

 #include  @interface Thing : NSObject @end // declare my vector std::vector<__weak Thing*> myThings; // store a weak reference in it Thing* t = [Thing new]; myThings.push_back(t); // ... some time later ... for(auto weak : myThings) { Thing* strong = weak; // safely lock the weak pointer if (strong) { // use the locked pointer } } 

Lo cual es equivalente a la expresión c ++:

 std::vector< std::weak_ptr > myCppThings; std::shared_ptr p = std::make_shared(); myCppThings.push_back(p); // ... some time later ... for(auto weak : myCppThings) { auto strong = weak.lock(); // safety is enforced in c++, you can't dereference a weak_ptr if (strong) { // use the locked pointer } } 

Prueba de concepto (a la luz de las preocupaciones de Tommy sobre la reasignación de vectores):

main.mm:

 #include  #import  @interface Thing : NSObject @end @implementation Thing @end extern void foo(Thing*); int main() { // declare my vector std::vector<__weak Thing*> myThings; // store a weak reference in it while causing reallocations Thing* t = [[Thing alloc]init]; for (int i = 0 ; i < 100000 ; ++i) { myThings.push_back(t); } // ... some time later ... foo(myThings[5000]); t = nullptr; foo(myThings[5000]); } void foo(Thing*p) { NSLog(@"%@", [p className]); } 

ejemplo de salida de registro:

 2016-09-21 18:11:13.150 foo2[42745:5048189] Thing 2016-09-21 18:11:13.152 foo2[42745:5048189] (null) 

Si no necesita un pedido específico, puede usar NSMapTable con NSMapTable especiales de clave / valor

NSPointerFunctionsWeakMemory

Utiliza barreras de lectura y escritura débiles apropiadas para ARC o GC. El uso de las referencias de objeto NSPointerFunctionsWeakMemory se convertirá en NULL en la última versión.

Creo que la mejor solución para esto es usar NSHashTable o NSMapTable. la clave o / y el valor pueden ser débiles. Puede leer más sobre esto aquí: http://nshipster.com/nshashtable-and-nsmaptable/

Para agregar una auto referencia débil a NSMutableArray, cree una clase personalizada con una propiedad débil como se indica a continuación.

 NSMutableArray *array = [NSMutableArray new]; Step 1: create a custom class @interface DelegateRef : NSObject @property(nonatomic, weak)id delegateWeakReference; @end Step 2: create a method to add self as weak reference to NSMutableArray. But here we add the DelegateRef object -(void)addWeakRef:(id)ref { DelegateRef *delRef = [DelegateRef new]; [delRef setDelegateWeakReference:ref] [array addObject:delRef]; } 

Paso 3: más adelante, si la propiedad delegateWeakReference == nil , el objeto se puede eliminar de la matriz

La propiedad será nula y las referencias se desasignarán en el momento adecuado independientemente de las referencias de este conjunto.

La solución más simple:

 NSMutableArray *array = (__bridge_transfer NSMutableArray *)CFArrayCreateMutable(nil, 0, nil); NSMutableDictionary *dictionary = (__bridge_transfer NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil); NSMutableSet *set = (__bridge_transfer NSMutableSet *)CFSetCreateMutable(nil, 0, nil); 

Nota: Y esto funciona en iOS 4.x también.

No, eso no es correcto. Esas no son realmente referencias débiles. Realmente no puedes almacenar referencias débiles en una matriz en este momento. Debe tener una matriz mutable y eliminar las referencias cuando haya terminado con ellas o eliminar la matriz completa cuando haya terminado con ella, o bien, implementar su propia estructura de datos que la soporte.

Esperemos que esto sea algo que abordarán en el futuro cercano (una versión débil de NSArray ).

Acabo de enfrentar el mismo problema y descubrí que mi solución antes de ARC funciona después de convertir con ARC según lo diseñado.

 // function allocates mutable set which doesn't retain references. NSMutableSet* AllocNotRetainedMutableSet() { CFMutableSetRef setRef = NULL; CFSetCallBacks notRetainedCallbacks = kCFTypeSetCallBacks; notRetainedCallbacks.retain = NULL; notRetainedCallbacks.release = NULL; setRef = CFSetCreateMutable(kCFAllocatorDefault, 0, &notRetainedCallbacks); return (__bridge NSMutableSet *)setRef; } // test object for debug deallocation @interface TestObj : NSObject @end @implementation TestObj - (id)init { self = [super init]; NSLog(@"%@ constructed", self); return self; } - (void)dealloc { NSLog(@"%@ deallocated", self); } @end @interface MainViewController () { NSMutableSet *weakedSet; NSMutableSet *usualSet; } @end @implementation MainViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization weakedSet = AllocNotRetainedMutableSet(); usualSet = [NSMutableSet new]; } return self; } - (IBAction)addObject:(id)sender { TestObj *obj = [TestObj new]; [weakedSet addObject:obj]; // store unsafe unretained ref [usualSet addObject:obj]; // store strong ref NSLog(@"%@ addet to set", obj); obj = nil; if ([usualSet count] == 3) { [usualSet removeAllObjects]; // deallocate all objects and get old fashioned crash, as it was required. [weakedSet enumerateObjectsUsingBlock:^(TestObj *invalidObj, BOOL *stop) { NSLog(@"%@ must crash here", invalidObj); }]; } } @end 

Salida:

2013-06-30 00: 59: 10.266 not_retained_collection_test [28997: 907] built 2013-06-30 00: 59: 10.267 not_retained_collection_test [28997: 907] addet para establecer 2013-06-30 00: 59: 10.581 not_retained_collection_test [28997: 907] built 2013-06-30 00: 59: 10.582 not_retained_collection_test [28997: 907] addet to set 2013-06-30 00: 59: 10.881 not_retained_collection_test [28997: 907] built 2013-06-30 00: 59: 10.882 not_retained_collection_test [28997: 907] addet to set 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] desasignado 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] desasignado 2013-06-30 00:59 : 10.884 not_retained_collection_test [28997: 907] desasignado 2013-06-30 00: 59: 10.885 not_retained_collection_test [28997: 907] * – [TestObj respondsToSelector:]: mensaje enviado a la instancia desasignada 0x1f03c8c0

Comprobado con las versiones de iOS 4.3, 5.1, 6.2. Espero que sea útil para alguien.

Si necesita poner a cero las referencias débiles, vea esta respuesta para el código que puede usar para una clase contenedora.

Otras respuestas a esa pregunta sugieren un contenedor basado en bloques y formas de eliminar automáticamente los elementos a cero de la colección.

Si usa mucho este comportamiento, se indica a su propia clase NSMutableArray (subclase de NSMutableArray) que no aumenta el recuento de retención.

Deberías tener algo como esto:

 -(void)addObject:(NSObject *)object { [self.collection addObject:[NSValue valueWithNonretainedObject:object]]; } -(NSObject*) getObject:(NSUInteger)index { NSValue *value = [self.collection objectAtIndex:index]; if (value.nonretainedObjectValue != nil) { return value.nonretainedObjectValue; } //it's nice to clean the array if the referenced object was deallocated [self.collection removeObjectAtIndex:index]; return nil; } 

Creo que una solución elegante es lo que el Sr. Erik Ralston propone en su repository Github

https://gist.github.com/eralston/8010285

estos son los pasos esenciales:

crear una categoría para NSArray y NSMutableArray

en la implementación crea una clase de conveniencia con una propiedad débil. Su categoría asignará los objetos a esta propiedad débil.

.h

  #import  @interface NSArray(WeakArray) - (__weak id)weakObjectForIndex:(NSUInteger)index; -(id)weakObjectsEnumerator; @end @interface NSMutableArray (FRSWeakArray) -(void)addWeakObject:(id)object; -(void)removeWeakObject:(id)object; -(void)cleanWeakObjects; @end 

.metro

 #import "NSArray+WeakArray.h" @interface WAArrayWeakPointer : NSObject @property (nonatomic, weak) NSObject *object; @end @implementation WAArrayWeakPointer @end @implementation NSArray (WeakArray) -(__weak id)weakObjectForIndex:(NSUInteger)index { WAArrayWeakPointer *ptr = [self objectAtIndex:index]; return ptr.object; } -(WAArrayWeakPointer *)weakPointerForObject:(id)object { for (WAArrayWeakPointer *ptr in self) { if(ptr) { if(ptr.object == object) { return ptr; } } } return nil; } -(id)weakObjectsEnumerator { NSMutableArray *enumerator = [[NSMutableArray alloc] init]; for (WAArrayWeakPointer *ptr in self) { if(ptr && ptr.object) { [enumerator addObject:ptr.object]; } } return enumerator; } @end @implementation NSMutableArray (FRSWeakArray) -(void)addWeakObject:(id)object { if(!object) return; WAArrayWeakPointer *ptr = [[WAArrayWeakPointer alloc] init]; ptr.object = object; [self addObject:ptr]; [self cleanWeakObjects]; } -(void)removeWeakObject:(id)object { if(!object) return; WAArrayWeakPointer *ptr = [self weakPointerForObject:object]; if(ptr) { [self removeObject:ptr]; [self cleanWeakObjects]; } } -(void)cleanWeakObjects { NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init]; for (WAArrayWeakPointer *ptr in self) { if(ptr && !ptr.object) { [toBeRemoved addObject:ptr]; } } for(WAArrayWeakPointer *ptr in toBeRemoved) { [self removeObject:ptr]; } } @end