La mise à zéro des références faibles nécessite OS X 10.7 ou iOS 5.
Vous ne pouvez définir des variables faibles que dans le code, les ivars ou les blocs. A priori, il n'y a aucun moyen de créer dynamiquement (à l'exécution) une variable faible car l'ARC prend effet au moment de la compilation. Lorsque vous exécutez le code, il a déjà les retenues et les libérations ajoutées pour vous.
Cela dit, vous pouvez probablement abuser des blocs pour obtenir un tel effet.
Avoir un bloc qui renvoie simplement la référence.
__weak id weakref = strongref;
[weakrefArray addObject:[^{ return weakref; } copy]];
Notez que vous devez copier le bloc pour qu'il soit copié dans le tas.
Maintenant vous pouvez parcourir le tableau quand vous le souhaitez, les objets désalloués dans les blocs retourneront nil. Vous pouvez alors les supprimer.
Vous ne pouvez pas faire en sorte que le code soit automatiquement exécuté lorsque la référence faible est mise à zéro. Si c'est ce que vous voulez, vous pouvez utiliser la fonction des objets associés. Ceux-ci sont désalloués en même temps que l'objet auquel ils sont associés. Vous pouvez donc avoir votre propre balise sentinelle qui informe la collection faible de la disparition de l'objet.
Vous auriez un objet associé à surveiller pour le dealloc (si l'association est la seule référence) et l'objet associé aurait un pointeur vers la collection à surveiller. Ensuite, lors du dealloc sentry, vous appelez la collection faible pour l'informer que l'objet surveillé a disparu.
Voici mon article sur les objets associés : http://www.cocoanetics.com/2012/06/associated-objects/
Voici ma mise en œuvre :
---- 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
Cela serait utilisé comme suit :
NSString *string = @"bla";
@autoreleasepool {
_weakCollection = [[DTWeakCollection alloc] init];
[_weakCollection checkInObject:string];
__object = [NSNumber numberWithInteger:1123333];
[_weakCollection checkInObject:__object];
}
Si vous affichez allObjects à l'intérieur du bloc autorelease pool, vous avez deux objets à l'intérieur. À l'extérieur, vous n'avez que la chaîne de caractères.
J'ai trouvé que dans le dealloc de l'entrée la référence de l'objet est déjà nil, donc vous ne pouvez pas utiliser __weak. A la place, j'utilise l'adresse mémoire de l'objet comme hash. Pendant que ceux-ci sont toujours dans _entries vous pouvez les traiter comme des objets réels et allObjects retourne un tableau autoreleased de références fortes.
Note : ceci n'est pas thread safe. Pour gérer les allocations sur les files d'attente/threads non principaux, vous devrez faire attention à synchroniser l'accès et la modification de l'ensemble des _entrées internes.
Note 2 : Ceci ne fonctionne actuellement qu'avec des objets entrant dans une seule collection faible, car une deuxième entrée écraserait la sentinelle associée. Si vous avez besoin de cela avec plusieurs collections faibles, alors la sentinelle devrait avoir un tableau de ces collections.
Note 3 : J'ai changé la référence de la sentinelle à la collection en faible également pour éviter un cycle de retenue.
Note 4 : Voici un typedef et des fonctions d'aide qui gèrent la syntaxe des blocs pour vous :
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 ();
}