web-dev-qa-db-fra.com

NSArray des références faibles (__unsafe_unretained) aux objets sous ARC

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

68
Emile Cormier

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.

73
yuji

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:

  1. Ne contient pas de référence forte à l'objet cible. Cela signifie que si l'objet n'a pas de références fortes pointant vers lui, l'objet sera désalloué.
  2. Si l'objet ref'd est désalloué, la référence faible deviendra nulle.

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).

55
Thomas Tempelmann

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)
25
Richard Hodges

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.

13
Erik Aigner

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/

10
Yaniv De Ridder

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

4
Harish Kumar Kailas

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.

4
ArtFeel

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).

3
Jason Coco

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:(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.

2
eug

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.

1
paulmelnikow

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;
}
1
Ciprian C