0

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

谢谢你的帮助。

4

3 回答 3

3

NSMutableSet不是线程安全的如果您希望从多个线程中访问一个,您必须自己强制执行一次访问。

这在线程编程指南中的“线程安全摘要”中有记录。

强制执行一次访问的典型方法是创建一个 GCD 队列(为每个集合)并仅从该队列访问集合(使用dispatch_sync或,如果可能的话dispatch_async)。在您的示例中,您将向您的类添加一个dispatch_queue_t实例变量,在 中对其进行初始化init,并在您的其他每个实例方法中使用它。

于 2014-04-21T16:19:50.017 回答
2

NSMutableSet在非线程安全的类中,因此它的方法应该被认为是非线程安全的,除非另有明确说明(此时没有任何NSMutableSet方法被记录为线程安全)。

我认为通过

使用该allObjects方法创建“快照”</p>

他们的意思是在锁后面创建一个快照,以避免在您枚举其对象并对其执行操作的整个过程中对整个集合持有锁。

于 2014-04-21T16:22:49.797 回答
1

您的另一个问题: [mySet allObjects] 返回一个包含集合中所有对象的 NSArray,而 [mySet copy] 返回一个 NSSet。如果您不需要集合的属性(成员资格非常快速的测试),则 NSArray 可能会更快一些。

于 2014-05-04T17:42:36.673 回答