NSArray de références faibles (__unsafe_unretained) aux objects sous ARC

J’ai besoin de stocker des références faibles aux objects dans un NSArray, afin d’éviter les cycles de rétention. Je ne suis pas sûr de la syntaxe appropriée à utiliser. C’est la bonne route?

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]; 

Notez que je dois prendre en charge iOS 4.x , donc __unsafe_unretained au lieu de __weak .


EDIT (2015-02-18):

Pour ceux qui veulent utiliser de vrais pointeurs __weak (pas __unsafe_unretained ), veuillez plutôt vérifier cette question: Collections de références faibles à la mise à zéro sous ARC

Comme Jason l’a dit, vous ne pouvez pas faire en sorte que NSArray stocke des références faibles. La méthode la plus simple pour implémenter la suggestion d’Emile consistant à encapsuler un object dans un autre object qui en contient une faible référence est la suivante:

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

Une autre option: une catégorie qui permet à NSMutableArray stocker éventuellement des références faibles.

Notez que ce sont des références “non sécurisées non sûres”, et non des références faibles à remise à zéro automatique. Si le tableau est toujours présent après la désallocation des objects, vous aurez un tas de pointeurs indésirables.

Les solutions pour utiliser un assistant NSValue ou pour créer un object collection (array, set, dict) et désactiver ses rappels Retain / Release ne sont pas des solutions 100% fiables en ce qui concerne l’utilisation d’ARC.

Comme le soulignent divers commentaires à ces suggestions, ces références d’object ne fonctionneront pas comme de véritables références faibles:

Une propriété “correcte”, prise en charge par ARC, a deux comportements:

  1. Ne détient pas une référence forte à l’object cible. Cela signifie que si l’object ne contient aucune référence forte, l’object sera libéré.
  2. Si l’object ref’d est désalloué, la référence faible deviendra nulle.

Maintenant, alors que les solutions ci-dessus seront conformes au comportement n ° 1, elles ne présentent pas le n ° 2.

Pour obtenir également le comportement n ° 2, vous devez déclarer votre propre classe d’assistance. Il a juste une propriété faible pour contenir votre référence. Vous ajoutez ensuite cet object d’assistance à la collection.

Oh, et encore une chose: iOS6 et OSX 10.8 sont censés offrir une meilleure solution:

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

Celles-ci devraient vous donner des récipients qui contiennent des références faibles (mais notez les commentaires de matt ci-dessous).

Je suis nouveau à objective-C, après 20 ans d’écriture de c ++.

À mon avis, objective-C est excellent pour la messagerie faiblement couplée, mais horrible pour la gestion des données.

Imaginez combien j’étais heureux de découvrir que xcode 4.3 supporte objective-c ++!

Alors maintenant, je renomme tous mes fichiers .m en .mm (comstack en tant que object-c ++) et utilise des conteneurs standard c ++ pour la gestion des données.

Ainsi, le problème “tableau de pointeurs faibles” devient un vecteur std :: de pointeurs object __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 } } 

Ce qui est équivalent à l’idiome 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 } } 

Preuve de concept (à la lumière des préoccupations de Tommy concernant la réallocation des vecteurs):

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]); } 

exemple de sortie de journal:

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

Si vous n’avez pas besoin d’un ordre spécifique, vous pouvez utiliser NSMapTable avec des options clé / valeur spéciales.

NSPointerFunctionsWeakMemory

Utilise des barrières de lecture et d’écriture faibles appropriées pour ARC ou GC. L’utilisation des références d’object NSPointerFunctionsWeakMemory se transformera en NULL lors de la dernière version.

Je pense que la meilleure solution consiste à utiliser NSHashTable ou NSMapTable. la clé ou / et la valeur peuvent être faibles. Vous pouvez en savoir plus à ce sujet ici: http://nshipster.com/nshashtable-and-nsmaptable/

Pour append une référence automatique faible à NSMutableArray, créez une classe personnalisée avec une propriété faible, comme indiqué ci-dessous.

 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]; } 

Etape 3: plus tard, si la propriété delegateWeakReference == nil , l’object peut être supprimé du tableau

La propriété sera nulle et les références seront désallouées au bon moment, indépendamment des références de ce tableau.

La solution la plus 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); 

Note: Et cela fonctionne aussi sur iOS 4.x.

Non, ce n’est pas correct. Ce ne sont pas vraiment des références faibles. Vous ne pouvez pas vraiment stocker les références faibles dans un tableau en ce moment. Vous devez disposer d’un tableau modifiable et supprimer les références lorsque vous en avez terminé avec elles ou supprimer l’ensemble du tableau lorsque vous en avez terminé ou déployer votre propre structure de données qui le prend en charge.

Avec un peu de chance, ils aborderont cette question dans un avenir proche (une version faible de NSArray ).

Je viens de faire face au même problème et j’ai constaté que ma solution avant-ARC fonctionne après la conversion avec ARC comme prévu.

 // 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:(NSSsortingng *)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 

Sortie:

2013-06-30 00: 59: 10.266 not_retained_collection_test [28997: 907] construit 2013-06-30 00: 59: 10.267 not_retained_collection_test [28997: 907] addet à définir 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 à définir 2013-06-30 00: 59: 10.881 not_retained_collection_test [28997: 907] construit 2013-06-30 00: 59: 10.882 not_retained_collection_test [28997: 907] addet à définir 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] désalloué 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] désalloué 2013-06-30 00:59 : 10.884 not_retained_collection_test [28997: 907] deallocated 2013-06-30 00: 59: 10.885 not_retained_collection_test [28997: 907] * – [TestObj respondsToSelector:]: message envoyé à l’instance désallouée 0x1f03c8c0

Vérifié avec les versions iOS 4.3, 5.1, 6.2. J’espère que ce sera utile à quelqu’un.

Si vous avez besoin de mettre à zéro des références faibles, consultez cette réponse pour le code que vous pouvez utiliser pour une classe wrapper.

D’autres réponses à cette question suggèrent un wrapper basé sur des blocs et des moyens de supprimer automatiquement les éléments mis à zéro de la collection.

Si vous utilisez beaucoup ce comportement, il est indiqué dans votre propre classe NSMutableArray (sous-classe de NSMutableArray) qui n’augmente pas le nombre de conservations.

Vous devriez avoir quelque chose comme ça:

 -(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; } 

Je pense qu’une solution élégante est ce que M. Erik Ralston propose sur son repository Github

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

ce sont les étapes essentielles:

créer une catégorie pour NSArray et NSMutableArray

Dans l’implémentation, créez une classe de commodité avec une propriété faible. Votre catégorie affectera les objects à cette propriété faible.

.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 

.m

 #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