Безопасен ли поток -allObjects в NSMutableSet?

Документация по NSSet objectEnumeration говорит:

Когда этот метод используется с изменяемыми подклассами NSSet, ваш код не должен изменять набор во время перечисления. Если вы собираетесь изменить набор, используйте метод allObjects для создания «моментального снимка» элементов набора. Перечислите снимок, но внесите свои изменения в исходный набор.

Теперь мой вопрос: Является ли сам метод allObjects потокобезопасным?

Я реализовал набор операций следующим образом:

@interface OperationSet : NSObject
@end
@implementation OperationSet
{
    NSMutableSet *_set;
}
- (instancetype)init
{
    self = [super init];
    if (self)
    {
        _set = [[NSMutableSet alloc] init];
    }
    return self;
}
- (void)addOperation:(Operation *)operation
{
    if (operation)
    {
        [_set addObject:operation];
    }
}
- (void)removeOperation:(Operation *)operation
{
    if (operation)
    {
        [_set removeObject:operation];
    }
}
- (void)removeAllOperations
{
    [_set removeAllObjects];
}
- (void)enumerateWithOperationBlock:(OperationBlock)block
{
    NSArray *allObjects = [_set allObjects];
    [allObjects enumerateObjectsUsingBlock:^(Operation *o, NSUInteger idx, BOOL *stop) {
        block(o);
    }];
}
- (void)flushCompletedOperations
{
    NSArray *allObjects = [_set allObjects];
    NSSet *safeSet = [NSSet setWithArray:allObjects];
    NSSet *completed = [safeSet objectsPassingTest:^BOOL(Operation *o, BOOL *stop){
        return o.completed;
    }];
    [_set minusSet:completed];
}
- (NSUInteger)count
{
    return [_set count];
}
- (BOOL)any:(OperationAnyBlock)block
{
    NSArray *allObjects = [_set allObjects];
    NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
        return block(o);
    }];
    return (index != NSNotFound);
}
- (Operation *)getOperationWithMatchingData:(NSDictionary *)data
{
    NSArray *allObjects = [_set allObjects];
    NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
        return [o matchesData:data];
    }];
    return (index == NSNotFound ? nil : allObjects[index]);
}
@end

Это все работает нормально. Но у меня через Crashlytics вылетает, что редко (два из сотен), но есть:

EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x0000000000000008
Thread : Crashed: com.apple.main-thread
0  CoreFoundation                 0x000000018772c438 -[__NSSetM addObject:] + 448
1  CoreFoundation                 0x000000018772c430 -[__NSSetM addObject:] + 440

Доступ к OperationSet осуществляется из нескольких потоков.

Любая помощь приветствуется.

ИЗМЕНИТЬ

Спасибо dasblinkenlight за освещение использования allObjects. Я отредактировал свою реализацию следующим образом:

@interface OperationSet : NSObject
@end
@implementation OperationSet
{
    NSMutableSet *_set;
    dispatch_queue_t _queue;
}
- (instancetype)init
{
    self = [super init];
    if (self)
    {
        _set = [[NSMutableSet alloc] init];
        _queue = dispatch_queue_create("OperationQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
- (void)addOperation:(Operation *)operation
{
    if (operation)
    {
        dispatch_async(_queue, ^{
            [_set addObject:operation];
        });
    }
}
- (void)removeOperation:(Operation *)operation
{
    if (operation)
    {
        dispatch_async(_queue, ^{
            [_set removeObject:operation];
        });
    }
}
- (void)removeAllOperations
{
    dispatch_async(_queue, ^{
        [_set removeAllObjects];
    });
}
- (void)enumerateWithOperationBlock:(OperationBlock)block
{
    __block NSArray *allObjects;
    dispatch_sync(_queue, ^{
        allObjects = [_set allObjects];
    });
    [allObjects enumerateObjectsUsingBlock:^(Operation *o, NSUInteger idx, BOOL *stop) {
        block(o);
    }];
}
- (void)flushCompletedOperations
{
    __block NSArray *allObjects;
    dispatch_sync(_queue, ^{
        allObjects = [_set allObjects];
    });
    NSSet *safeSet = [NSSet setWithArray:allObjects];
    NSSet *completed = [safeSet objectsPassingTest:^BOOL(Operation *o, BOOL *stop){
        return o.completed;
    }];
    [_set minusSet:completed];
}
- (NSUInteger)count
{
    return [_set count];
}
- (BOOL)any:(OperationAnyBlock)block
{
    __block NSArray *allObjects;
    dispatch_sync(_queue, ^{
        allObjects = [_set allObjects];
    });
    NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
        return block(o);
    }];
    return (index != NSNotFound);
}
- (Operation *)getOperationWithMatchingData:(NSDictionary *)data
{
    __block NSArray *allObjects;
    dispatch_sync(_queue, ^{
        allObjects = [_set allObjects];
    });
    NSUInteger index = [allObjects indexOfObjectPassingTest:^BOOL(Operation *o, NSUInteger idx, BOOL *stop) {
        return [o matchesData:data];
    }];
    return (index == NSNotFound ? nil : allObjects[index]);
}
@end

Код работает! Это хороший знак, но не могли бы вы пересмотреть его?

И еще один вопрос: Есть ли разница между использованием allObjects и созданием набора копий?

Это использование этого кода:

- (void)enumerateWithOperationBlock:(OperationBlock)block
{
    __block NSArray *allObjects;
    dispatch_sync(_queue, ^{
        allObjects = [_set allObjects];
    });
    [allObjects enumerateObjectsUsingBlock:^(Operation *o, NSUInteger idx, BOOL *stop) {
        block(o);
    }];
}

над этим кодом:

- (void)enumerateWithOperationBlock:(OperationBlock)block
{
    __block NSSet *safeSet;
    dispatch_sync(_queue, ^{
        safeSet = [_set copy];
    });
    [safeSet enumerateObjectsUsingBlock:^(Operation *o, BOOL *stop) {
        block(o);
    }];
}

Спасибо за вашу помощь.


person reggian    schedule 21.04.2014    source источник


Ответы (3)


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

Это описано в "Thread Safety Summary" в Руководство по программированию потоков.

Типичный способ принудительного доступа по одному — создание одной очереди GCD (для каждого набора) и доступ к набору только из этой очереди (используя dispatch_sync или, если возможно, dispatch_async). В вашем примере вы должны добавить переменную экземпляра dispatch_queue_t в свой класс, инициализировать ее в init и использовать ее в каждом из ваших других методов экземпляра.

person rob mayoff    schedule 21.04.2014
comment
Благодарю за ваш ответ. Пожалуйста, смотрите мой отредактированный вопрос. Ваше здоровье - person reggian; 21.04.2014
comment
@rob-mayoff У меня похожая проблема, для которой я не нашел способа реализовать очередь GCD (сейчас я использую блокировку). У меня есть класс, который действует как оболочка для вектора С++. У него есть методы, которые добавляют элементы (нажимают на вектор) — их легко поставить в очередь. Однако мне также нужен регулярный доступ к указателю вектора непосредственно в некоторых математических методах в другом классе в фоновом потоке (например, запись/чтение tic-toc). Прямо сейчас мое решение состоит в том, чтобы получатель указателя блокировал класс (поэтому новые данные добавляются во временный массив), а математический вызов метода разблокировался по завершении. Как бы я использовал здесь очередь? - person Adam Jones; 02.05.2014
comment
@ Адам Вы должны опубликовать новый вопрос, а не комментарий к моему ответу. - person rob mayoff; 02.05.2014
comment
@robmayoff выполнено: stackoverflow.com/questions/23435895/ - person Adam Jones; 02.05.2014

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

я думаю, что по

используйте метод allObjects для создания «моментального снимка»

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

person Sergey Kalinichenko    schedule 21.04.2014

Другой ваш вопрос: [mySet allObjects] возвращает NSArray, содержащий все объекты в наборе, а [копия mySet] возвращает NSSet. Если вам не нужны свойства набора (очень быстрый тест на членство), NSArray, вероятно, будет немного быстрее.

person gnasher729    schedule 04.05.2014