1

エラーが発生します

* キャッチされない例外 'NSGenericException' が原因でアプリを終了します。理由: '*コレクション <__NSCFSet: 0x6b66390> が列挙中に変更されました。

クラスに新しいデリゲートを追加するとき。または、少なくとも、そこに問題があると思います。

これは私のコードです: MyAppAPI.m

[...]
static NSMutableSet *_delegates = nil;

@implementation MyAppAPI

+ (void)initialize
{
    if (self == [MyAppAPI class]) {
        _delegates = [[NSMutableSet alloc] init];
    }
}

+ (void)addDelegate:(id)delegate
{
    [_delegates addObject:delegate];
}

+ (void)removeDelegate:(id)delegate
{
    [_delegates removeObject:delegate];
}
[...]

@end

MyAppAPI は、アプリケーション全体で使用できるシングルトンです。私ができる (またはできるはずの) どこでも: [MyAppAPI addDelegate:self].
これはうまく機能しますが、最初のビューでのみです。このビューには、新しいビューをそれ自体にロードする PageViewController を備えた UIScrollView があります。これらの新しいビューは、MyAppAPI に登録して、アンロードされるまでメッセージをリッスンします (この場合は を実行しますremoveDelegate)。ただし、UIScrollView の 2 番目のビューで addDelegate を実行した直後に停止するようです。

これが起こらないようにコードを改善するにはどうすればよいですか?

更新
もう少し明確にしたいと思います。ビューコントローラー「StartPage」には、ページコントローラーを備えた UIScrollView があります。他のいくつかのビューをロードします (現在表示されている画面の 1 つ前)。各ビューはインスタント PageViewController であり、上記の addDelegate 関数を使用してそれ自体を MyAppAPI というグローバル シングルトンに登録します。ただし、私が理解しているように、ビューコントローラー2がそれ自体を登録するときに、このビューコントローラー1はまだデリゲートから読み取っているため、上記のエラーが表示されます。

シナリオを明確にしたことを願っています。いくつか試してみましたが、何も役に立ちません。デリゲートから読み取り中であっても、addDelegate を使用してデリゲートに登録する必要があります。それ、どうやったら出来るの?

更新 2 これはレスポンダー メソッドの 1 つです。

+ (void)didRecieveFeaturedItems:(NSArray*)items
{   
    for (id delegate in _delegates)
    {
        if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
            [delegate didRecieveFeaturedItems:items];
    }
}
4

6 に答える 6

11

スコット・ハンターは正しい。このエラーは、反復中にリストを編集しようとするとスローされます。

ここにあなたがしているかもしれないことの例があります。

+ (void)iteratingToRemove:(NSArray*)items {   
    for (id delegate in _delegates) {
        if(delegate.removeMePlease) {
          [MyAppAPI removeDelegate:delegate];  //error you are editing an NSSet while enumerating
        }
    }
}

そして、これを正しく処理する方法は次のとおりです。

+ (void)iteratingToRemove:(NSArray*)items
{   
    NSMutableArray *delegatesToRemove = [[NSMutableArray alloc] init];
    for (id delegate in _delegates) {
        if(delegate.removeMePlease) {
          [delegatesToRemove addObject:delegate];
        }
    }

    for(id delegate in delegatesToRemove) {
         [MyAppAPI removeDelegate:delegate];  //This works better
    }

    [delegatesToRemove release];
}
于 2011-11-02T00:56:12.583 に答える
5

このエラーは、どこかのコードがリストを調べている最中に、リストを変更していることを示しています (これは addDelegate が呼び出された後のクラッシュを説明しています)。列挙を行うコードがリストを変更するコードである場合、列挙が完了するまで (たとえば、それらを別のリストにまとめるなどして) 変更を延期する必要があります。列挙を行うコードについて何も知らなければ、それ以上のことは言えません。

于 2011-10-27T21:12:46.017 に答える
2

A simple solution, don't use a mutable set. They are dangerous for a variety of reasons, including this one.

You can use -copy and -mutableCopy to convert between mutable and non-mutable versions of NSSet (and many other classes). Beware all copy methods return a new object with a retain count of 1 (just like alloc), so you need to release them.

Aside from having less potential for bugs, non-mutable objects are faster to work with and use less memory.

[...]
static NSSet *_delegates = nil;

@implementation MyAppAPI

+ (void)initialize
{
    if (self == [MyAppAPI class]) {
        _delegates = [[NSSet alloc] init];
    }
}

+ (void)addDelegate:(id)delegate
{
    NSMutableSet *delegatesMutable = [_delegates mutableCopy];
    [delegatesMutable addObject:delegate];

    [_delegates autorelease];
    _delegates = [delegatesMutable copy];

    [delegatesMutable release];
}

+ (void)removeDelegate:(id)delegate
{
    NSMutableSet *delegatesMutable = [_delegates mutableCopy];
    [delegatesMutable removeObject:delegate];

    [_delegates autorelease];
    _delegates = [delegatesMutable copy];

    [delegatesMutable release];
}
[...]

@end
于 2011-11-07T11:58:03.130 に答える
1

Scott Hunter の言うとおりです。セットの項目を列挙しているときに NSSet を変更するのは問題です。アプリケーションがクラッシュした場所からのスタック トレースが必要です。おそらく、_delegates セットに追加/削除する行があるでしょう。これは、変更を加える必要がある場所です。やり方は簡単です。セットへの追加/セットからの削除の代わりに、次の操作を行います。

NSMutableSet *tempSet = [_delegates copy];
for (id delegate in _delegates)
{
    //add or remove from tempSet instead
}
[_delegates release], _delegates = tempSet;

さらに、NSMutableSet はスレッド セーフではないため、常にメイン スレッドからメソッドを呼び出す必要があります。余分なスレッドを明示的に追加していない場合は、心配する必要はありません。

于 2011-11-03T02:19:27.523 に答える
0

このエラーは、他のスレッドが配列を反復処理しているときに、スレッドが配列を変更(追加、削除)しようとしたときに発生します。

NSLockを使用するか、メソッドを同期することでこれを解決する1つの方法。この方法では、メソッドの追加、削除、反復を並行して呼び出すことはできません。ただし、追加/削除は配列を反復処理していたスレッドを待機する必要があるため、これはパフォーマンスや応答性に影響します。

JavaのCopyOnWriteArrayListから着想を得たより良い解決策は、配列のコピーを作成し、そのコピーを反復処理することです。したがって、コードの唯一の変更は次のようになります。-

//better solution
+ (void)didRecieveFeaturedItems:(NSArray*)items
{   
    NSArray *copyOfDelegates = [_delegates copy]
    for (id delegate in copyOfDelegates)
    {
        if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
            [delegate didRecieveFeaturedItems:items];
    }
}

パフォーマンスに影響を与えるロックを使用したソリューション

//not a good solution

+ (void)addDelegate:(id)delegate
{
    @synchronized(self){
        [_delegates addObject:delegate];
    }
}

+ (void)removeDelegate:(id)delegate
{
    @synchronized(self){
        [_delegates removeObject:delegate];
   }
}

+ (void)didRecieveFeaturedItems:(NSArray*)items
{   
    @synchronized(self){
        for (id delegate in _delegates)
        {
            if ([delegate respondsToSelector:@selector(didRecieveFeaturedItems:)])
                [delegate didRecieveFeaturedItems:items];
        }
    }
}
于 2011-11-05T07:49:14.933 に答える
0

Objective-C の「高速列挙」について常に覚えておくべきこと。
「高速列挙」と for ループの間には 2 つの大きな違いがあります。

「高速列挙」は for ループよりも高速です。
しかし
、列挙しているコレクションを変更することはできません。

- (NSArray *)allObjectsNSSetを変更しながら、その配列をNSSet に要求して列挙することができます。

于 2011-11-04T15:50:38.060 に答える