NSArray слабых ссылок (__unsafe_unretained) для объектов под ARC

Мне нужно хранить слабые ссылки на объекты в NSArray, чтобы предотвратить циклы сохранения. Я не уверен в правильном использовании синтаксиса. Правильно ли это?

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

Обратите внимание, что мне нужно поддерживать iOS 4.x, поэтому __unsafe_unretained вместо __weak.


EDIT (2015-02-18):

Для тех, кто хочет использовать истинные указатели __weak (не __unsafe_unretained), проверьте этот вопрос: Коллекции обнуления слабых ссылок в ARC p >

Ответ 1

Как сказал Джейсон, вы не можете заставить NSArray хранить слабые ссылки. Самый простой способ реализовать предложение Emile об обтекании объекта внутри другого объекта, который хранит слабую ссылку на него, следующий:

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

Еще одна опция: category, которая позволяет NSMutableArray хранить слабые ссылки.

Обратите внимание, что это "небезопасные неохваченные" ссылки, а не самообносящиеся слабые ссылки. Если массив все еще находится вокруг после того, как объекты будут освобождены, у вас будет куча указателей нежелательной почты.

Ответ 2

Решения для использования помощника NSValue или для создания объекта коллекции (массив, набор, dict) и отключить его обратные вызовы Retain/Release являются не 100% отказоустойчивыми решениями в отношении использования ARC.

Как отмечают различные комментарии к этим предложениям, такие ссылки на объекты не будут работать как истинные слабые ссылки:

"Собственное" слабое свойство, поддерживаемое ARC, имеет два поведения:

  • Не содержит ссылки на целевой объект. Это означает, что если у объекта нет сильных ссылок, указывающих на него, объект будет освобожден.
  • Если объект ref'd освобожден, слабая ссылка станет нулевой.

Теперь, хотя приведенные выше решения будут соответствовать поведению №1, они не показывают # 2.

Чтобы получить поведение # 2, вы должны объявить свой собственный вспомогательный класс. У этого есть только одно слабое свойство для того, чтобы держать Вашу рекомендацию. Затем вы добавляете этот вспомогательный объект в коллекцию.

О, и еще одна вещь: iOS6 и OSX 10.8 предположительно предлагают лучшее решение:

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

Они должны предоставить вам контейнеры, содержащие слабые ссылки (но обратите внимание на матовые комментарии ниже).

Ответ 3

Я новичок в objective-C, после 20 лет написания С++.

На мой взгляд, objective-C отлично подходит для слабосвязанных сообщений, но ужасно для управления данными.

Представьте, как я рад узнать, что xcode 4.3 поддерживает objective-c ++!

Итак, теперь я переименовываю все мои .m файлы в .mm(компилируется как objective-c ++) и использует стандартные контейнеры С++ для управления данными.

Таким образом, проблема "массив слабых указателей" становится std::vector указателей объектов __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
  }
}

Что эквивалентно идиоме С++:

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

Доказательство концепции (в свете озабоченности Томми о перераспределении вектора):

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

Пример вывода журнала:

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

Ответ 4

Если вам не нужен конкретный заказ, вы можете использовать NSMapTable со специальными параметрами ключ/значение

NSPointerFunctionsWeakMemory

Использует слабые барьеры чтения и записи, подходящие для ARC или GC. Использование ссылок на объекты NSPointerFunctionsWeakMemory в последней версии будет отображаться в NULL.

Ответ 5

Я считаю, что лучшим решением для этого является использование NSHashTable или NSMapTable. ключ или/и значение могут быть слабыми. Подробнее об этом можно прочитать здесь: http://nshipster.com/nshashtable-and-nsmaptable/

Ответ 6

Простейшее решение:

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

Примечание: И это тоже работает на iOS 4.x.

Ответ 7

Чтобы добавить слабую ссылку на NSMutableArray, создайте собственный класс со слабым свойством, как показано ниже.

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

}

Шаг 3: позже, если свойство delegateWeakReference == nil, объект можно удалить из массива

Свойство будет равно nil, и ссылки будут освобождены в надлежащее время независимо от ссылок на этот массив

Ответ 8

Нет, это не правильно. На самом деле это не слабые ссылки. Вы не можете хранить слабые ссылки в массиве прямо сейчас. Вам нужно иметь изменяемый массив и удалять ссылки, когда вы закончите с ними, или удалите весь массив, когда закончите с ним, или сверните свою собственную структуру данных, которая ее поддерживает.

Надеюсь, это то, к чему они обратятся в ближайшем будущем (слабая версия NSArray).

Ответ 9

Я столкнулся с такой же проблемой и обнаружил, что мое решение перед ARC работает после преобразования с ARC, как было разработано.

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

Вывод:

2013-06-30 00: 59: 10.266 not_retained_collection_test [28997: 907]  построено 2013-06-30 00: 59: 10.267 not_retained_collection_test [28997: 907] addet to set 2013-06-30 00: 59: 10.581 not_retained_collection_test [28997: 907]  построено 2013-06-30 00: 59: 10.582 not_retained_collection_test [28997: 907] addet to set 2013-06-30 00: 59: 10.881 not_retained_collection_test [28997: 907]  построено 2013-06-30 00: 59: 10.882 not_retained_collection_test [28997: 907] addet to set 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907]  deallocated 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] deallocated 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 отвечаетSoSelector:]: сообщение отправлено на освобожденный экземпляр 0x1f03c8c0

Проверено с версиями iOS 4.3, 5.1, 6.2. Надеюсь, это будет полезно кому-то.

Ответ 10

Если вам нужны обнуление слабых ссылок, см. этот ответ для кода, который вы можете использовать для класса-оболочки.

Другие ответы на этот вопрос предлагают оболочку на основе блоков и способы автоматического удаления обнуленных элементов из коллекции.

Ответ 11

Если вы много используете этот контент, он указывает ваш собственный класс NSMutableArray (подкласс NSMutableArray), который не увеличивает счет сохранения.

У вас должно быть что-то вроде этого:

-(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 nice to clean the array if the referenced object was deallocated
    [self.collection removeObjectAtIndex:index];

    return nil;
}

Ответ 12

Я думаю, что изящное решение - это то, что г-н Эрик Ралстон предлагает в своем репозитории Github

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

Это важные шаги:

создать категорию для NSArray и NSMutableArray

в реализации создайте класс удобства со слабым свойством. Ваша категория будет присваивать объекты этому слабому свойству.

.h

 #import <Foundation/Foundation.h>

@interface NSArray(WeakArray)

- (__weak id)weakObjectForIndex:(NSUInteger)index;
-(id<NSFastEnumeration>)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<NSFastEnumeration>)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