このクラッシュをほぼ1週間修正しようとしています。アプリケーションは、例外やスタックトレースなしでクラッシュします。ゾンビモードでインストゥルメントを実行中に、アプリケーションがクラッシュすることはありません。
別のスレッドで呼び出されるメソッドがあります。クラッシュを修正したソリューションは置き換えられました
[self.mutableArray removeAllObjects];
と
dispatch_async(dispatch_get_main_queue(), ^{
[self.searchResult removeAllObjects];
});
私はそれがタイミングの問題かもしれないと思ったので、それを同期しようとしましたが、それでもクラッシュしました:
@synchronized(self)
{
[self.searchResult removeAllObjects];
}
ここにコードがあります
- (void)populateItems
{
// Cancel if already exists
[self.searchThread cancel];
self.searchThread = [[NSThread alloc] initWithTarget:self
selector:@selector(populateItemsinBackground)
object:nil];
[self.searchThread start];
}
- (void)populateItemsinBackground
{
@autoreleasepool
{
if ([[NSThread currentThread] isCancelled])
[NSThread exit];
[self.mutableArray removeAllObjects];
// Populate data here into mutable array
for (loop here)
{
if ([[NSThread currentThread] isCancelled])
[NSThread exit];
// Add items to mutableArray
}
}
}
NSMutableArrayのこの問題はスレッドセーフではありませんか?
いや.
スレッドセーフではありません。別のスレッドから可変配列を変更する必要がある場合は、NSLock
を使用してすべてが計画どおりに進むようにする必要があります。
NSLock *arrayLock = [[NSLock alloc] init];
[...]
[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:@"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];
他の人がすでに言ったように、NSMutableArrayはスレッドセーフではありません。スレッドセーフ環境でremoveAllObject以外のものを実現したい場合は、ロックを使用するソリューション以外にGCDを使用する別のソリューションを提供します。あなたがしなければならないことは、read/update(replace/remove)アクションを同期することです。
まず、グローバルな並行キューを取得します。
dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
読み取り用:
- (id)objectAtIndex:(NSUInteger)index {
__block id obj;
dispatch_sync(self.concurrent_queue, ^{
obj = [self.searchResult objectAtIndex:index];
});
return obj;
}
挿入の場合:
- (void)insertObject:(id)obj atIndex:(NSUInteger)index {
dispatch_barrier_async(self.concurrent_queue, ^{
[self.searchResult insertObject:obj atIndex:index];
});
}
From Apple dispatch_barrier_asyncに関するドキュメント:
バリアブロックがプライベートコンカレントキューの先頭に到達しても、すぐには実行されません。代わりに、キューは現在実行中のブロックの実行が完了するまで待機します。その時点で、バリアブロックは自動的に実行されます。バリアブロックの後に送信されたブロックは、バリアブロックが完了するまで実行されません。
削除についても同様:
- (void)removeObjectAtIndex:(NSUInteger)index {
dispatch_barrier_async(self.concurrent_queue, ^{
[self.searchResult removeObjectAtIndex:index];
});
}
[〜#〜] edit [〜#〜]:実際、今日提供されているシリアルキューを使用して、リソースへのアクセスを同期する別の簡単な方法を見つけましたGCD。
From Apple Doc 同時実行プログラミングガイド>ディスパッチキュー :
シリアルキューは、タスクを特定の順序で実行する場合に便利です。シリアルキューは、一度に1つのタスクのみを実行し、常にキューの先頭からタスクをプルします。共有リソースまたは可変データ構造を保護するために、ロックの代わりにシリアルキューを使用する場合があります。ロックとは異なり、シリアルキューは、タスクが予測可能な順序で実行されるようにします。また、タスクを非同期でシリアルキューに送信する限り、キューがデッドロックすることはありません。
シリアルキューを作成します。
dispatch_queue_t myQueue = dispatch_queue_create("com.example.MyQueue", NULL);
非同期のタスクをシリアルキューにディスパッチします。
dispatch_async(myQueue, ^{
obj = [self.searchResult objectAtIndex:index];
});
dispatch_async(myQueue, ^{
[self.searchResult removeObjectAtIndex:index];
});
それが役に立てば幸い!
NSLock
も@synchronized
(condition-object)を使用できるのと同様に、配列は、同じコンテンツのみを変更する場合、condition-objectとして機能する同じオブジェクトで@synchronized
にラップされます配列インスタンスは、配列自体をcondition-objectとして使用できます。親オブジェクト、つまりselfは、同じ配列に対して常に同じものになるため、適切な選択です。
@property
属性のatomicは、コンテンツを変更せずに配列のスレッドセーフを設定するだけです。つまり、self.mutableArray
= ...はスレッドセーフですが、[self.mutableArray removeObject:]
はそうではありません。
__weak typeof(self)weakSelf = self;
@synchronized (weakSelf.mutableArray) {
[weakSelf.mutableArray removeAllObjects];
}
シリアルキューが言及されていたので:可変配列では、「スレッドセーフか」と尋ねるだけでは十分ではありません。たとえば、removeAllObjectsがクラッシュしないようにすることは問題ありませんが、別のスレッドが同時に配列を処理しようとすると、配列を処理しますbeforeまたはafterすべての要素が削除され、実際に動作がどうあるべきかを考える必要があります。
この配列を担当する1つのクラス+オブジェクトを作成し、シリアルキューを作成し、そのシリアルキューのクラスを介してすべての操作を行うことが、同期の問題で頭を痛めることなく物事を正しく行う最も簡単な方法です。
NSMutablexxxクラスはすべてスレッドセーフではありません。 get、insert、remove、add、replaceなどの操作はNSLockで使用する必要があります。これは、Appleが提供するスレッドセーフクラスとスレッドセーフでないクラスのリストです。 Thread Safety Summary
ほとんどのNSMutableクラスオブジェクトはスレッドセーフではありません。