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);
}];
}
谢谢你的帮助。