it-swarm.com.ru

Коллекции обнуления слабых ссылок под ARC

Как я могу получить массив обнуление слабых ссылок в ARC? Я не хочу, чтобы массив сохранял объекты. И я бы хотел, чтобы элементы массива либо удаляли себя, когда они освобождены, либо устанавливали эти записи равными nil.

Точно так же, как я могу сделать это со словарем? Я не хочу, чтобы словарь сохранял значения. И снова, я хотел бы, чтобы элементы словаря либо удаляли себя, когда значения были освобождены, либо устанавливали значения в ноль. (Мне нужно сохранить ключи, которые являются уникальными идентификаторами, по крайней мере до тех пор, пока соответствующие значения не будут освобождены.)

Эти два вопроса охватывают схожее основание:

Но ни один не запрашивает обнуление ссылок.

Согласно документации, ни NSPointerArray, ни NSHashMap не поддерживают слабые ссылки в ARC. NSValue nonretainedObjectValue также не будет работать, так как он не равен нулю.

Единственное решение, которое я вижу, - это создать мой собственный NSValue-подобный класс-оболочку со свойством (weak), как этот ответ упоминается ближе к концу . Есть ли лучший способ, которого я не вижу?

Я занимаюсь разработкой для OS X 10.7 и iOS 6.0.

40
paulmelnikow

Вот код для обнуления класса-оболочки со слабой ссылкой. Он работает правильно с NSArray, NSSet и NSDictionary.

Преимущество этого решения в том, что оно совместимо со старыми ОС и все просто. Недостатком является то, что при итерации вам, вероятно, нужно проверить, что -nonretainedObjectValue не равен nil, прежде чем использовать его.

Это та же идея, что и в первой части ответа Cocoanetics, в которой используются блоки для достижения того же результата.

WeakReference.h

@interface WeakReference : NSObject {
    __weak id nonretainedObjectValue;
    __unsafe_unretained id originalObjectValue;
}

+ (WeakReference *) weakReferenceWithObject:(id) object;

- (id) nonretainedObjectValue;
- (void *) originalObjectValue;

@end

WeakReference.m

@implementation WeakReference

- (id) initWithObject:(id) object {
    if (self = [super init]) {
        nonretainedObjectValue = originalObjectValue = object;
    }
    return self;
}

+ (WeakReference *) weakReferenceWithObject:(id) object {
    return [[self alloc] initWithObject:object];
}

- (id) nonretainedObjectValue { return nonretainedObjectValue; }
- (void *) originalObjectValue { return (__bridge void *) originalObjectValue; }

// To work appropriately with NSSet
- (BOOL) isEqual:(WeakReference *) object {
    if (![object isKindOfClass:[WeakReference class]]) return NO;
    return object.originalObjectValue == self.originalObjectValue;
}

@end
17
paulmelnikow

Обнуление слабых ссылок требует OS X 10.7 или iOS 5.

Вы можете определять слабые переменные только в коде, иваре или блоках. AFAIK нет способа динамически (во время выполнения) создать слабую переменную, потому что ARC вступает в силу во время компиляции. Когда вы запускаете код, к нему уже добавляются сохранения и выпуски.

Сказав, что вы, вероятно, можете использовать блоки, чтобы добиться такого эффекта.

Есть блок, который просто возвращает ссылку.

__weak id weakref = strongref;
[weakrefArray addObject:[^{ return weakref; } copy]];

Обратите внимание, что вам нужно скопировать блок, чтобы скопировать его в кучу.

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

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

У вас был бы один связанный объект для отслеживания сделки (если ассоциация является единственной ссылкой), а связанный объект имел бы указатель на просмотр коллекции. Затем в часовом диллоке вы вызываете слабую коллекцию, чтобы сообщить ей, что наблюдаемый объект исчез.

Вот моя рецензия на связанные объекты: http://www.cocoanetics.com/2012/06/associated-objects/

Вот моя реализация:

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

Это будет использоваться так:

NSString *string = @"bla";

@autoreleasepool {
_weakCollection = [[DTWeakCollection alloc] init];
    [_weakCollection checkInObject:string];

__object = [NSNumber numberWithInteger:1123333];

[_weakCollection checkInObject:__object];
}

если вы выводите allObjects внутри блока пула автоматического выпуска, то у вас там есть два объекта. Снаружи у вас есть только строка.

Я обнаружил, что в dealloc записи ссылка на объект уже равна нулю, поэтому вы не можете использовать __weak. Вместо этого я использую адрес памяти объекта как хеш. Пока они все еще находятся в _entries, вы можете рассматривать их как фактический объект, и allObjects возвращает автоматически выпущенный массив надежных ссылок.

Примечание: это не потокобезопасно. Обращайтесь с dealloc в неосновных очередях/потоках, вам нужно быть осторожным, чтобы синхронизировать доступ и изменение внутреннего набора _entries.

Примечание 2: в настоящее время это работает только с объектами, проверяющими одну слабую коллекцию, так как вторая регистрация перезапишет связанный часовой. Если вам нужно это с несколькими слабыми коллекциями, тогда у часового должен быть массив этих коллекций.

Примечание 3: Я изменил ссылку часового на коллекцию на слабую, чтобы избежать цикла сохранения. 

Примечание 4: Вот функции typedef и helper, которые обрабатывают синтаксис блока для вас:

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 ();
}
24
Cocoanetics

NSMapTable должен работать на вас. Доступно в iOS 6.

8
Tricertops
@interface Car : NSObject
@end
@implementation Car
-(void) dealloc {
    NSLog(@"deallocing");
}
@end


int main(int argc, char *argv[])
{
    @autoreleasepool {
        Car *car = [Car new];

        NSUInteger capacity = 10;
        id __weak *_objs = (id __weak *)calloc(capacity,sizeof(*_objs));
        _objs[0] = car;
        car = nil;

        NSLog(@"%p",_objs[0]);
        return EXIT_SUCCESS;
    }
}

Результат:

2013-01-08 10:00:19.171 X[6515:c07] deallocing
2013-01-08 10:00:19.172 X[6515:c07] 0x0

edit: Я создал образец коллекции слабых карт с нуля на основе этой идеи. Это работает, но уродливо по нескольким причинам:

Я использовал категорию в NSObject , чтобы добавить @properties для ключа, следующего сегмента карты и ссылку на коллекцию, владеющую объектом.

Как только вы обнулите объект, он исчезнет из коллекции. 

НО, чтобы карта имела динамическую емкость, необходимо получить обновление количества элементов, чтобы рассчитать коэффициент загрузки и расширить емкость при необходимости. То есть, если вы не хотите выполнять обновление Θ (n), повторяя весь массив каждый раз, когда добавляете элемент. Я сделал это с помощью обратного вызова метода dealloc объекта-образца, который я добавляю в коллекцию. Я мог бы отредактировать исходный объект (что я сделал для краткости) или унаследовать от суперкласса, или заняться деллок. В любом случае, некрасиво.

Однако, если вы не возражаете против сбора фиксированной емкости, вам не нужны обратные вызовы. Коллекция использует отдельную цепочку и при условии равномерного распределения хеш-функции производительность будет Θ (1 + n/m), где n = элементы, m = емкость. Но (больше но), чтобы избежать разрыва цепочки, вам нужно добавить предыдущую ссылку в качестве категории @property и связать ее со следующим элементом в dealloc элемента. И как только мы дотронемся до dealloc, так же хорошо уведомить коллекцию о том, что элемент удаляется (что сейчас и делается). 

Наконец, обратите внимание, что тест в проекте минимален, и я мог что-то пропустить.

3
Jano

Я просто создаю не-потокобезопасную версию со слабой ссылкой NSMutableDictionary и NSMutableSet. Код здесь: https://Gist.github.com/4492283

Для NSMutableArray все сложнее, потому что он не может содержать nil и объект может быть добавлен в массив несколько раз. Но это осуществимо для реализации.

2
Bryan Chen

Просто добавьте категорию для NSMutableSet со следующим кодом: 

@interface WeakReferenceObj : NSObject
@property (nonatomic, weak) id weakRef;
@end

@implementation WeakReferenceObj
+ (id)weakReferenceWithObj:(id)obj{
    WeakReferenceObj *weakObj = [[WeakReferenceObj alloc] init];
    weakObj.weakRef = obj;
    return weakObj;
}
@end

@implementation NSMutableSet(WeakReferenceObj)
- (void)removeDeallocRef{
    NSMutableSet *deallocSet = nil;
    for (WeakReferenceObj *weakRefObj in self) {
        if (!weakRefObj.weakRef) {
            if (!deallocSet) {
                deallocSet = [NSMutableSet set];
            }
            [deallocSet addObject:weakRefObj];
        }
    }
    if (deallocSet) {
        [self minusSet:deallocSet];
    }
}

- (void)addWeakReference:(id)obj{
    [self removeDeallocRef];
    [self addObject:[WeakReferenceObj weakReferenceWithObj:obj]];
}
@end

Тот же способ создать категорию для NSMutableArray и NSMutableDictionary.

Удалите ссылку dealloc в didReceiveMemoryWarning будет лучше.

- (void)didReceiveMemoryWarning{
    [yourWeakReferenceSet removeDeallocRef];
}

Затем вам нужно вызвать addWeakReference: для вашего класса контейнера.

2
simalone

Если вы работаете как минимум с MacOS X 10.5 или iOS6, то:

  • NSPointerArray weakObjectsPointerArray/pointerArrayWithWeakObjects является слабой ссылочной подставкой для NSArray
  • NSHashTable hashTableWithWeakObjects/weakObjectsHashTable является слабым эталонным стандартом для NSSet
  • NSMapTable является слабой ссылочной подставкой для NSDictionary (может иметь слабые ключи и/или слабые значения)

Обратите внимание, что коллекции могут не сразу заметить, что объекты исчезли, поэтому число может быть еще больше, а ключи все еще могут существовать, даже если связанный объект исчез, и т.д. NSPointerArray имеет -компактный метод, который теоретически должен избавиться от любых указатели Документы NSMapTable отмечают, что ключи для карт weakToStrong будут оставаться в таблице (даже если они фактически равны нулю) до тех пор, пока не будет изменен размер, то есть указатели сильных объектов могут оставаться в памяти, даже если на них больше нет логической ссылки.

Правка: я вижу оригинальный постер спросил о ARC. Я думаю, что на самом деле это были 10.8 и iOS 6, прежде чем эти контейнеры могли использоваться с ARC - предыдущий "слабый" материал был для GC, я думаю. ARC не поддерживался до 10.7, так что это действительно вопрос, нужно ли вам поддерживать этот выпуск, а не 10.6, и в этом случае вам нужно будет свернуть свой собственный (или, возможно, использовать пользовательские функции с NSPointerFunctions, которые затем могут быть использованы в свою очередь). с NSPointerArray, NSHashTable и NSMapTable).

2
Carl Lindberg

Смотрите BMNullableArray class, который является частью моей среды BMCommons для полного решения этой проблемы.

Этот класс позволяет вставлять nil-объекты и имеет возможность слабо ссылаться на содержащиеся в нем объекты (автоматически обнуляя их, когда они освобождаются).

Проблема с автоматическим удалением (которую я пытался реализовать) заключается в том, что у вас возникают проблемы с безопасностью потоков, поскольку не гарантируется, в какой момент времени объекты будут освобождены, что также может произойти при итерации массива.

Этот класс является улучшением по сравнению с NSPointerArray, так как он абстрагирует некоторые детали более низкого уровня и позволяет работать с объектами вместо указателей. Он даже поддерживает NSFastEnumeration для перебора массива с нулевыми ссылками.

0
Werner Altewischer