NSArray de referências fracas (__unsafe_unretained) para objects sob ARC

Eu preciso armazenar referências fracas para objects em um NSArray, a fim de evitar ciclos de retenção. Não tenho certeza da syntax correta a ser usada. Esta é a forma correta?

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

Note que eu preciso suportar o iOS 4.x , assim o __unsafe_unretained vez de __weak .


EDIT (2015-02-18):

Para aqueles que querem usar pointers __weak verdadeiros (não __unsafe_unretained ), por favor, verifique esta questão: Coleções de zerar referências fracas sob ARC

Como Jason disse, você não pode tornar as referências fracas da loja NSArray . A maneira mais fácil de implementar a sugestão de Emile de envolver um object dentro de outro object que armazena uma referência fraca é o seguinte:

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

Outra opção: uma categoria que torna o NSMutableArray opcionalmente, armazena referências fracas.

Note que estas são referências “inseguras não-retidas”, não referências fracas de auto-zeramento. Se a matriz ainda estiver por aí depois que os objects forem desalocados, você terá um monte de pointers de lixo eletrônico.

As soluções para usar um NSValue helper ou para criar um object de coleta (array, set, dict) e desabilitar seus retornos de chamada Retain / Release não são soluções 100% seguras contra falhas no uso do ARC.

Como vários comentários a estas sugestões apontam, tais referências de objects não funcionarão como verdadeiras referências fracas:

Uma propriedade fraca “adequada”, conforme suportada pelo ARC, possui dois comportamentos:

  1. Não possui uma referência forte ao object alvo. Isso significa que, se o object não tiver referências fortes apontando para ele, o object será desalocado.
  2. Se o object ref. For desalocado, a referência fraca se tornará nula.

Agora, embora as soluções acima estejam em conformidade com o comportamento nº 1, elas não exibem o nº 2.

Para obter o comportamento 2 também, você deve declarar sua própria class auxiliar. Tem apenas uma propriedade fraca para guardar sua referência. Em seguida, você adiciona esse object auxiliar à coleção.

Ah, e mais uma coisa: o iOS6 e o ​​OSX 10.8 supostamente oferecem uma solução melhor:

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

Estes devem dar contêineres com referências fracas (mas observe os comentários de Matt abaixo).

Eu sou novo no objective-C, depois de 20 anos escrevendo c ++.

Na minha opinião, o objective-C é excelente em mensagens fracamente acopladas, mas horrível para o gerenciamento de dados.

Imagine como fiquei feliz ao descobrir que o xcode 4.3 suporta o object-c ++!

Agora, renomeio todos os meus arquivos .m para .mm (compila como objective-c ++) e uso contêineres padrão c ++ para gerenciamento de dados.

Assim, o problema “matriz de pointers fracos” torna-se um ponteiro de object std :: vector of __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 } } 

Qual é equivalente ao idioma do 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 } } 

Prova de conceito (à luz das preocupações de Tommy sobre a realocação de vetores):

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

exemplo de saída de log:

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

Se você não precisa de um pedido específico, você pode usar o NSMapTable com opções especiais de chave / valor

NSPointerFunctionsWeakMemory

Usa barreiras fracas de leitura e gravação apropriadas para ARC ou GC. O uso de referências a objects NSPointerFunctionsWeakMemory mudará para NULL no último release.

Eu acredito que a melhor solução para isso é usar NSHashTable ou NSMapTable. a chave ou / e o valor pode ser fraco. Você pode ler mais sobre isso aqui: http://nshipster.com/nshashtable-and-nsmaptable/

Para adicionar auto referência fraca a NSMutableArray, crie uma class personalizada com uma propriedade fraca, conforme indicado abaixo.

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

Etapa 3: mais tarde, se a propriedade delegateWeakReference == nil , o object puder ser removido da matriz

A propriedade será nula e as referências serão desalocadas no momento adequado, independentemente das referências a essa matriz

A solução mais simples:

 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: E isso funciona no iOS 4.x também.

Não, isso não está correto. Essas não são realmente referências fracas. Você não pode realmente armazenar referências fracas em uma matriz agora. Você precisa ter um array mutável e remover as referências quando terminar com elas ou remover toda a matriz quando terminar, ou rolar sua própria estrutura de dados que a suporte.

Espero que isso seja algo que eles abordarão no futuro próximo (uma versão fraca do NSArray ).

Acabei de me deparar com o mesmo problema e descobri que a minha solução antes do ARC funciona após a conversão com o ARC conforme concebido.

 // 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 

Saída:

2013-06-30 00: 59: 10.266 not_retained_collection_test [28997: 907] construído em 2013-06-30 00: 59: 10.267 not_retained_collection_test [28997: 907] addet para definir 2013-06-30 00: 59: 10.581 not_retained_collection_test [28997: 907] construído em 2013-06-30 00: 59: 10.582 not_retained_collection_test [28997: 907] addet para definir 2013-06-30 00: 59: 10.881 not_retained_collection_test [28997: 907] construído em 2013-06-30 00: 59: 10.882 not_retained_collection_test [28997: 907] addet para definir 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] desalocado 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] desalocado 2013-06-30 00:59 : 10.884 not_retained_collection_test [28997: 907] desalocados 2013-06-30 00: 59: 10.885 not_retained_collection_test [28997: 907] * – [TestObj respondersToSelector:]: mensagem enviada para a instância desalocada 0x1f03c8c0

Verificado com as versões iOS 4.3, 5.1, 6.2. Espero que seja útil para alguém.

Se você precisar zerar referências fracas, veja esta resposta para o código que você pode usar para uma class de wrapper.

Outras respostas a essa pergunta sugerem um wrapper baseado em bloco e maneiras de remover automaticamente elementos zerados da coleção.

Se você usa muito este comportamento, ele é indicado para sua própria class NSMutableArray (subclass de NSMutableArray) que não aumenta a contagem de retenções.

Você deveria ter algo assim:

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

Eu acho que uma solução elegante é o que o Sr. Erik Ralston propõe em seu repository Github

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

Estes são os passos essenciais:

criar uma categoria para NSArray e NSMutableArray

na implementação, crie uma class de conveniência com uma propriedade fraca. Sua categoria atribuirá os objects a essa propriedade fraca.

.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 
Intereting Posts