有关NSSet objectEnumeration says的文档:
当此方法与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);
}];
}
感谢您的帮助。
答案 0 :(得分:3)
NSMutableSet
不是线程安全的。如果您希望从多个线程访问一个,您必须自己实施一次一个访问。
“Thread Safety Summary” in the Threading Programming Guide中记录了这一点。
强制执行一次一次访问的典型方法是创建一个GCD队列(对于每个集合)并仅从该队列访问该集合(使用dispatch_sync
,或者,如果可能,{{1 }})。在您的示例中,您将向类中添加dispatch_async
实例变量,在dispatch_queue_t
中对其进行初始化,并在其他每个实例方法中使用它。
答案 1 :(得分:2)
NSMutableSet
是listed among the classes that are not thread-safe,因此除非另有明确说明,否则应将其方法视为非线程安全的(此时NSMutableSet
方法均未记录为线程安全的)。
我认为是
使用
allObjects
方法创建“快照”
它们意味着在锁定后面创建一个快照,以避免在整个集合期间锁定整个集合,以便枚举其对象并对它们执行操作。
答案 2 :(得分:1)
您的另一个问题:[mySet allObjects]返回包含集合中所有对象的NSArray,而[mySet copy]返回NSSet。如果您不需要集合的属性(非常快速的成员资格测试),NSArray可能会更快一点。