[英]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.