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を介してクラッシュしました。これはまれです(数百分の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 は、複数のスレッドからアクセスされます。

どんな助けでも大歓迎です。

編集

allObjects の使用法を教えてくれた dasblinkenlight に感謝します。実装を次のように編集しました。

@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

コードが機能します!これは良い兆候ですが、見直していただけますか?

もう 1 つの質問があります。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

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 に答える