NSArray слабых ссылок (__unsafe_unretained) на объекты в ARC - PullRequest
68 голосов
/ 18 февраля 2012

Мне нужно хранить слабые ссылки на объекты в 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.


РЕДАКТИРОВАТЬ (2015-02-18):

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

Ответы [ 12 ]

73 голосов
/ 18 февраля 2012

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

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

Другой вариант: категория , которая составляет NSMutableArrayПри желании можно хранить слабые ссылки.

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

54 голосов
/ 13 ноября 2012

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

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

«Правильное» слабое свойство, поддерживаемое ARC, имеет два поведения:

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

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

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

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

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

Это должно дать вам контейнеры со слабыми ссылками (но обратите внимание на комментарии Мэтта ниже).

24 голосов
/ 12 февраля 2013

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

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

Представьте, как я был рад обнаружить, что xcode 4.3 поддерживает Objective-C ++!

Итак, теперь я переименовываю все мои файлы .m в .mm (компилируется как target-c ++) и использую стандартные контейнеры 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
  }
}

Что эквивалентно идиоме 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
  }
}

Подтверждение концепции (в свете озабоченности Томми по поводу перераспределения векторов):

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)
13 голосов
/ 04 апреля 2014

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

NSPointerFunctionsWeakMemory

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

10 голосов
/ 21 ноября 2013

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

4 голосов
/ 17 февраля 2015

Чтобы добавить слабую собственную ссылку на 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, и ссылки будут освобождены в нужное время независимо от ссылок на этот массив

4 голосов
/ 29 октября 2014

Самое простое решение:

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 тоже.

3 голосов
/ 18 февраля 2012

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

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

2 голосов
/ 30 июня 2013

Я только что столкнулся с той же проблемой и обнаружил, что мое решение до 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] и набор 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] и набор 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] и набор 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] освобожден 2013-06-30 00: 59: 10.883 not_retained_collection_test [28997: 907] освобожден 2013-06-30 00: 59: 10.884 not_retained_collection_test [28997: 907] освобожден 2013-06-30 00: 59: 10.885 not_retained_collection_test [28997: 907] * - [TestObj respdsToSelector:]: сообщение отправлено освобожденному экземпляру 0x1f03c8c0

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

1 голос
/ 05 ноября 2015

Если вы много используете этот компортмент, он указывается вашему собственному классу 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's nice to clean the array if the referenced object was deallocated
    [self.collection removeObjectAtIndex:index];

    return nil;
}
...