5

私のアプリケーションでは、新しいレコードを適切な場所に追加する前に、2 つのコア データ ストアからすべてのレコードを削除しています (または削除しようとしています)。これらは、アドレス帳のレコードに関連するデータを含む 2 つの単純なストアです (VIContacts には連絡先 ID と vcard ハッシュ (整数) が含まれ、VIGroup にはグループ ID とグループ名が含まれます)。

ストアからすべての連絡先を削除するには、次のコードをメソッドで使用します-clear:


NSArray *allOldRowsInVIContacts = [[mainContext fetchObjectsForEntityName:[VIContact name]
                                               includePropertyValues:NO
                                               withPredicate:nil] copy];

for (NSManagedObject *obj in allOldRowsInVIContacts) {
    @try {
        [mainContext deleteObject:obj];
    }
    @catch (NSException *exception) {
        NSLog(@"Exception Triggered: %@", exception.reason);
        [NSException raise:exception.reason format:@"thrown on vicontacts."];
    }
}

[allOldRowsInVIContacts release];

if (![mainContext save:error]) {
    return NO;
}

NSArray *allOldRowsInVIGroups = [[mainContext fetchObjectsForEntityName:[VIGroup name]
                                                 includePropertyValues:NO
                                                         withPredicate:nil] copy];

NSLog(@"all rows in VIGroups count: %d", [allOldRowsInVIGroups count]);

for (NSManagedObject *obj in allOldRowsInVIGroups) {
    @try {
        [mainContext deleteObject:obj];
    }
    @catch (NSException *exception) {
        NSLog(@"Exception Triggered: %@", exception.reason);
        [NSException raise:exception.reason format:@"thrown on vigroups."];
    }
}

[allOldRowsInVIGroups release];

NSLog(@"at the end of -clear: Going to save context.");

/* SAVE */
if (![mainContext save:error]) {
    return NO;
}

アプリは常に VIGroup 領域でクラッシュするようです。

クラッシュログは次のとおりです。


Crashed Thread:  5  Dispatch queue: com.apple.root.default-priority

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000000
...
Thread 5 Crashed:: Dispatch queue: com.apple.root.default-priority
0   com.apple.CoreFoundation        0x00007fff82532574 __CFBasicHashRehash + 1412
1   com.apple.CoreFoundation        0x00007fff8252b41b __CFBasicHashAddValue + 75
2   com.apple.CoreFoundation        0x00007fff82531f78 CFBasicHashAddValue + 3176
3   com.apple.CoreFoundation        0x00007fff82547899 CFSetAddValue + 121
4   com.apple.CoreData              0x00007fff8520e3dc -[NSManagedObjectContext deleteObject:] + 220
5   com.andrei.AddressBookApp       0x000000010004da9a -[AddressBookFrameworkSyncHelper clear:] + 490
6   com.andrei.AddressBookApp       0x000000010004c8f9 +[AddressBookFrameworkSyncHelper saveSnapshot:] + 105
7   com.andrei.AddressBookApp       0x000000010002d417 -[SLSyncOperation main] + 2631
8   com.apple.Foundation            0x00007fff8b68dbb6 -[__NSOperationInternal start] + 684

他の情報

Instruments を使用してゾンビを探しましたが、何も表示されません。アプリケーションにいくつかのリークがありますが、Core Data に関連するものはありません。

VIGroup と VIContact の間に関係はありません。それらは独立した独立したエンティティです。

Exception triggered: ...奇妙なことに、コンソールはクラッシュする前にメッセージを取得しないため、コードは @catch に入らないようです。

エラーは時々スローされます。アプリは Lion の方が安定しているようですが、Mountain Lion と Snow Leopard では頻繁にクラッシュします。

ありがとう。どんな助けでも大歓迎です。

いくつかのコードで更新

作成された MOC:

「NSOperation」(「SLSyncOperation」) を作成し、「NSOperationQueue」に追加します。それSLSyncOperationはに追加されますNSOperationQueue


[backgroundQueue setMaxConcurrentOperationCount:1];

// has a custom initializer
currentOperation = [[SLSyncOperation alloc] initWithPersistentStoreCoordinator:persistentStoreCoordinator
                                                                   andDelegate:delegate
                                                               forceRemoteSync:forceSync];

[backgroundQueue addOperation:currentOperation];

これは SLSyncOperation のmainメソッドです (NSOperation から継承):


- (void)main {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    syncProgressTracker = [SLSyncProgressTracker sharedProgressTracker];
    syncProgressTracker.currentStatus = SLSyncStatusIdle;

    // ... some other setup and sending notifications ...

    /* Set up. */
    managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    managedObjectContext = [[NSManagedObjectContext alloc] init];

    // persistentStoreCoordinator is passed from the app delegate
    [managedObjectContext setPersistentStoreCoordinator:persistentStoreCoordinator];

    // ... continues with other logic (syncing to a server), and the end of the method is: ...

    /* Tear down. */
    [managedObjectContext release];
    managedObjectModel = nil;

    [pool drain];
}

使用されている MOC:

内から呼び出されるメソッドから呼び出されるシングルトン クラスで MOC を使用していSLSyncOperationます。この場合、すべてが同じスレッドで起こっていると思います...? これを確認するために、いくつかのテスト メソッドを追加します。

シングルトン クラスで初期化されている MOC:


+ (AddressBookFrameworkSyncHelper *)sharedHelper {
    if (!_sharedAddressBookHelper) {
        _sharedAddressBookHelper = [[AddressBookFrameworkSyncHelper alloc] init];
    }

    return _sharedAddressBookHelper;
}

- (id)init {
    if (self = [super init]) {        
        mainContext = [(AddressBookAppAppDelegate *)[[NSApplication sharedApplication] delegate] managedObjectContext];
        addressBookRef = [ABAddressBook sharedAddressBook];

        // disable undo manager - uses less memory
        [mainContext setUndoManager:nil];        
    }

    return self;
}

この後、MOC ( mainContext) を使用して保存し、それを使用する他のメソッドに渡します。


//saving
[sharedABF.mainContext save:error];

// passing it to a Core Data method
VIContact *contactToAdd = [VIContact newOrExistingContactWithID:contactID
                                                      inContext:sharedABF.mainContext
                                                          error:error];

// that method looks like this
+ (VIContact *)newOrExistingContactWithID:(NSString *)contactID inContext:(NSManagedObjectContext *)context error:(NSError **)error {    
    VIContact *theContact = [[context fetchObjectsForEntityName:[VIContact name]
                                          includePropertyValues:YES
                                                  withPredicate:
                              @"personID == %@", contactID] lastObject];

    if (theContact) {
        return [theContact retain];
    } else {
        // no contact found with that ID, return a new one
        VIContact *newContact = [[VIContact alloc] initAndInsertInContext:context];
        newContact.personID = contactID;
        return newContact;
    }
}

// and then fetch all rows in a Core Data entity and remove them
NSArray *allOldRowsInVIContacts = [mainContext fetchObjectsForEntityName:[VIContact name]
                                                   includePropertyValues:NO
                                                           withPredicate:nil];

for (NSManagedObject *obj in allOldRowsInVIContacts) {
    [mainContext deleteObject:obj];
}

if (![mainContext save:error]) {
    return NO;
}

fetchObjectsForEntityNameメソッドはここから取得されます

あなたが言及したメソッドを使用して、メソッドが異なるスレッドからアクセスされるかどうかを確認します。これが役に立ち、私が MOC をどのように使用しているかについての詳細情報を提供してくれることを願っています。

詳しくは

mainContext が作成されるスレッドの名前をSLSyncOperationThread.Name set.. アプリがクラッシュするポイントの前に、スレッドの名前を出力する NSLog を配置しました。毎回このスレッドの名前を出力します。したがって、マルチスレッドの問題ではないようです。特に、アプリがその時点に達するたびに時々クラッシュするためです。

4

3 に答える 3

6

オブジェクトの代わりにファイルを削除してみてください。

- (void)emptyDatabase{
    NSError * error;
    // retrieve the store URL
    NSURL * storeURL = [[self.managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[self.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
    // lock the current context
    [self.managedObjectContext lock];
    [self.managedObjectContext reset];//to drop pending changes
    //delete the store from the current managedObjectContext
    if ([[self.managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[self.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error])
    {
        // remove the file containing the data
        [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
        //recreate the store like in the  appDelegate method
        [[self.managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
    }
    [self.managedObjectContext unlock];
}
于 2012-09-03T11:24:23.923 に答える
4

まず、エンティティは関係を共有していないとおっしゃっていますが、エンティティにはどのような関係がありますか?

次に、NULL ポインターが逆参照されているため、セグメンテーション違反が発生しています。

3 番目に、いくつの NSManagedObjectContext オブジェクトがあり、どのようにアクセスされていますか?

clearメソッドが内から呼び出されているように見えますNSOperationQueueが、メイン スレッドで実行されていません。複数のスレッドから同時に単一の MOC にアクセスすることは想定されていません。

私の最初の賭け (詳細情報なし) は、複数のスレッドから MOC にアクセスしていることですが、これは非常に悪いことです。

また、Matt Gallagher の 1 行フェッチを使用しているようです。NSSetではなく、 a を返すと思いますNSArray...この場合は問題にならないようですが、適切な型を使用していることを確認することは常に良いことです。

編集

残念ながら、メソッドを呼び出す方法は示されていませんclear。あなたがコメントに置き換えたものにあるに違いありません:

// ... continues with other logic (syncing to a server), and the end of the method is: ...

いずれにせよ、私の最初の賭けは正しかったと確信しており、あなたはmainContext別のスレッドの MOC を使用しています。SLSyncOperation は独自のスレッドで実行され、独自の MOC (それが作成するもの) を使用しています。ただし、その操作内では、その中で使用するために作成された MOC ではなく、どの呼び出しが使用されているかをsaveSnapshot呼び出しているように見えます。clearmainContextNSOperation

これmainContextは、AppDelegate が所有する MOC であり、メイン スレッドの処理に使用されます。この「他の」スレッドでも呼び出され、使用されています。証拠としてスタック トレースを参照してください。それが Core Data とスレッドのルール 1 です。複数のスレッドが同じ MOC に同時にアクセスすることを許可してはなりません。

したがって、別のスレッドをスピンして何らかの作業を行い、ローカル MOC を作成して作業を行います。すべて順調です。ただし、そのスレッドから、 を明示的に使用するメソッドを呼び出していますmainContext(この場合saveSnapshotは、操作のmain.

ここで、いくつかのオプションがあります。

ローカルで生成された MOC をこれらのメソッドに渡して、適切な MOC で動作するようにします。代わりに、その MOC のスナップショットを作成するつもりですか?

これらのメソッドが MOC で動作することを本当に意図している場合は、mainContextそれらがメイン スレッドで実行されることを確認する必要があります。私はperformセレクターが好きではなく、直接の GCD を好みます。

dispatch_async(dispatch_get_main_queue(), ^{
    // Now you can call the saveSnapshot and other stuff that must use
    // mainContext since stuff in this block will execute on the main thread.
});

メインの MOC を使用していて、他のスレッドがある場合は、メインの MOC に限定同時実行を使用しないことを強くお勧めします。代わりに、次の代替案を提案します。

managedObjectContext = [[NSManagedObjectContext alloc]
                        initWithConcurrencyType:NSMainQueueCurrencyType];

現在、その MOC は、コードを変更することなく、他の MOC と同じように使用できます (つまり、メイン スレッドで使用された場合でも、閉じ込め MOC のように適切に動作します)。ただし、他のスレッドからもより適切に使用できます。

[managedObjectContext performBlock:^{
    // Do anything with this MOC because its protected, and
    // running on the right thread.
}];

必要に応じて、performBlockAndWaitどちらが再入可能であるかを呼び出すことができます (dispatch_sync は再入可能ではありません- デッドロックが発生します)。sync回避するために操作に注意する必要がありますが、デッドロックすることなく同じスレッドから再帰的に呼び出すことdeadly embraceperformBlockAndWaitできます。

于 2012-09-05T15:06:30.957 に答える
4

2 つのスレッド間で NSManagedObjectContext を共有しているようです。これは単なる推測ですが、スレッド 5 でクラッシュが発生していることを考えると、これが問題である可能性が高いと思われます。NSManagedObjectContext は 1 つのスレッドでのみ使用できます。mainContextメインスレッドで作成していて、何らかの理由でclearメソッドがバックグラウンドスレッドで呼び出されていると推測しています。

考えられる解決策は次のとおりです。

  1. バックグラウンド スレッドで使用する新しい NSManagedObjectContext を作成します。
  2. clearメソッドがメインスレッドで呼び出されていることを確認できます。これを確実に行う方法の 1 つを次に示します。

    -(void)clear:(id)object {
        if(![[NSThread currentThread] isMainThread]) {
            [self performSelectorOnMainThread:@selector(clear:) withObject:object waitUntilDone:NO];
            return;
        }
        ...
    }
    
  3. 理論的には、NSManagedObjectContext を使用できlockますunlock。これは、私が最もお勧めしないソリューションです。

このスレッドを読むことも役立つかもしれません。

于 2012-09-05T18:11:34.437 に答える