簡體   English   中英

NSMutableSet線程上的-allObjects是否安全?

[英]Is -allObjects on NSMutableSet thread safe?

關於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遇到了崩潰,這種情況很少見(每100例中有2例),但是仍然存在:

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和制作set副本有什么區別嗎?

那就是使用這段代碼:

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

謝謝你的幫助。

NSMutableSet 不是線程安全的。 如果希望從多個線程訪問一個線程,則必須自己一次強制執行一次訪問。

《線程編程指南 》的“線程安全摘要”中對此進行了記錄。

一次強制執行一次訪問的典型方法是創建一個GCD隊列(針對每個集合),然后僅從該隊列訪問該集合(使用dispatch_sync或(如果可能的話,使用dispatch_async ))。 在您的示例中,您將在類中添加一個dispatch_queue_t實例變量,在init對其進行init ,並在其他每個實例方法中使用它。

NSMutableSet 列在非線程安全的類中 ,因此,除非另有明確說明,否則應將其方法視為非線程安全的(此時NSMutableSet方法均未記錄為線程安全的)。

我認為

使用allObjects方法創建“快照”

他們的意思是在鎖后創建快照,以避免在枚舉對象並對其執行操作的整個過程中對整個集合保持鎖。

您的另一個問題:[mySet allObjects]返回一個包含該集合中所有對象的NSArray,而[mySet copy]返回一個NSSet。 如果您不需要集合的屬性(非常快速的成員資格測試),則NSArray可能會更快一些。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM