J'ai besoin de stocker des références faibles à des objets 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 le __unsafe_unretained
au lieu de __weak
.
[~ # ~] modifier [~ # ~] (2015-02-18):
Pour ceux qui souhaitent utiliser true __weak
pointeurs (pas __unsafe_unretained
), veuillez plutôt vérifier cette question: Collections de mise à zéro des références faibles sous ARC
Comme Jason l'a dit, vous ne pouvez pas faire en sorte que NSArray
stocke des références faibles. La façon la plus simple de mettre en œuvre la suggestion d'Emile de placer un objet dans un autre objet qui stocke une référence faible à celui-ci est la suivante:
NSValue *value = [NSValue valueWithNonretainedObject:myObj];
[array addObject:value];
Autre option: une catégorie qui fait que NSMutableArray
stocke éventuellement des références faibles.
Notez qu'il s'agit de références "non sécurisées non conservées", et non de références faibles à mise à zéro automatique. Si le tableau est toujours là après la désallocation des objets, vous aurez un tas de pointeurs indésirables.
Les solutions pour utiliser un NSValue helper ou pour créer une collection (array, set, dict) object and disable its Retain/Release callbacks ne sont pas tous les deux des solutions de sécurité à 100% en ce qui concerne l'utilisation de l'ARC.
Comme le soulignent divers commentaires sur ces suggestions, ces références d'objet ne fonctionneront pas comme de véritables références faibles:
Une propriété faible "appropriée", telle que prise en charge par ARC, a deux comportements:
Maintenant, bien que les solutions ci-dessus soient conformes au comportement # 1, elles ne présentent pas # 2.
Pour obtenir le comportement n ° 2 également, vous devez déclarer votre propre classe d'assistance. Il n'a qu'une seule propriété faible pour contenir votre référence. Vous ajoutez ensuite cet objet d'assistance à la collection.
Oh, et encore une chose: iOS6 et OSX 10.8 offrent censément une meilleure solution:
[NSHashTable weakObjectsHashTable]
[NSPointerArray weakObjectsPointerArray]
[NSPointerArray pointerArrayWithOptions:]
Ceux-ci devraient vous donner des conteneurs qui contiennent des références faibles (mais notez les commentaires de matt ci-dessous).
Je suis nouveau sur objective-C, après 20 ans d'écriture en c ++.
À mon avis, l'objectif-C est excellent pour la messagerie à couplage lâche, mais horrible pour la gestion des données.
Imaginez à quel point j'étais heureux de découvrir que xcode 4.3 prend en charge objective-c ++!
Alors maintenant, je renomme tous mes fichiers .m en .mm (compile en objective-c ++) et utilise des conteneurs standard c ++ pour la gestion des données.
Ainsi, le problème du "tableau de pointeurs faibles" devient un vecteur std :: de pointeurs d'objets __weak:
#include <vector>
@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 équivaut à l'idiome c ++:
std::vector< std::weak_ptr<CppThing> > myCppThings;
std::shared_ptr<CppThing> p = std::make_shared<CppThing>();
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éaffectation des vecteurs):
main.mm:
#include <vector>
#import <Foundation/Foundation.h>
@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'une commande spécifique, vous pouvez utiliser NSMapTable
avec des options de clé/valeur spéciales
NSPointerFunctionsWeakMemory
Utilise de faibles barrières de lecture et d'écriture appropriées pour ARC ou GC. L'utilisation des références d'objet NSPointerFunctionsWeakMemory passera à NULL lors de la dernière version.
Je crois que la meilleure solution pour cela est d'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 ajouter une auto-référence 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];
}
Étape 3: plus tard, si la propriété delegateWeakReference == nil
, l'objet peut être supprimé du tableau
La propriété sera nulle et les références seront désallouées au moment approprié, indépendamment de ces références de 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);
Remarque: Et cela fonctionne aussi sur iOS 4.x.
Non, ce n'est pas correct. Ce ne sont pas des références faibles. Vous ne pouvez pas vraiment stocker de références faibles dans un tableau pour le moment. Vous devez disposer d'un tableau modifiable et supprimer les références lorsque vous en avez terminé avec eux ou supprimer tout le tableau lorsque vous en avez terminé, ou rouler votre propre structure de données qui le prend en charge.
J'espère que c'est quelque chose qu'ils aborderont 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,
¬RetainedCallbacks);
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
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] construit 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ésaffecté 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] désaffecté 2013-06-30 00:59 : 10.884 not_retained_collection_test [28997: 907] désalloué 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 cela sera utile à quelqu'un.
Si vous avez besoin de la mise à 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é à votre propre classe NSMutableArray (sous-classe de NSMutableArray), ce qui n'augmente pas le nombre de rétentions.
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;
}