在Core Data存储上使用deleteObject:时的EXC_BAD_ACCESS(SIGSEGV)

时间:2012-08-31 10:00:44

标签: objective-c macos core-data exc-bad-access

在我的应用程序中,我正在删除(或尝试删除)来自两个核心数据存储的所有记录,然后再添加新的记录。它们是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之间没有任何关系。它们是独立的独立实体。

奇怪的是,代码似乎永远不会进入@catch,因为控制台在崩溃之前没有收到任何Exception triggered: ...消息。

错误会不时被抛出。该应用程序似乎在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方法取自here

我将尝试查看是否使用您提到的那些方法从不同的线程访问该方法。希望这会有所帮助,并为您提供有关我如何使用MOC的更多信息。

更多信息

我已将创建mainContext的线程命名为SLSyncOperationThread.Name set.。在应用程序崩溃之前,我放了一个NSLog,它打印出线程的名称。它每次打印出这个帖子的名字。所以它似乎不是一个多线程问题。特别是因为应用程序每次都会在每次到达该点时崩溃。

3 个答案:

答案 0 :(得分: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];
}

答案 1 :(得分:4)

首先,你说实体不共享关系,但他们有什么关系?

其次,您正在获取分段错误,因为正在取消引用NULL指针。

第三,你有多少个NSManagedObjectContext对象,以及它们是如何被访问的?

看起来你的clear方法是在NSOperationQueue内调用的,但它并没有在主线程上运行。您不应该同时从多个线程访问单个MOC。

我最初的赌注(没有进一步的信息)是你从多个线程访问MOC,这是一件非常糟糕的事情。

此外,看起来你正在使用Matt Gallagher的单行提取。我认为它返回NSSet,而不是NSArray ...在这种情况下似乎并不重要,但总是很好,以确保使用正确的类型。

修改

很遗憾,您没有说明如何调用clear方法。我打赌这是你用评论取代的东西:

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

无论如何,我确信我的第一个赌注是正确的,而你正在使用来自不同线程的mainContext MOC。您的SLSyncOperation在其自己的线程中运行,并且正在使用自己的MOC(它创建的MOC)。但是,在该操作中,它似乎是调用saveSnapshot调用clear,而mainContext反过来使用NSOperation,而不是创建用于mainContext的MOC }}

mainContext是AppDelegate拥有的MOC,用于主线程中的东西。它也在这个“其他”线程中被调用和使用。查看堆栈跟踪以获取证据。这是核心数据和线程的第1号规则。您不得允许多个线程并发访问同一个MOC。

所以,你正在转动一个单独的线程做一些工作,并创建一个本地MOC来完成工作。一切都很好。但是,您仍然可以从该线程调用明确要使用saveSnapshot的方法(在这种情况下main直接从您的操作mainContext调用。

现在,您有几种选择:

将懒惰生成的MOC传递给那些方法,以便它们在适当的MOC上运行。也许你打算快照那个MOC?

如果你真的打算让这些方法在perform MOC上运行,那么你需要确保它们在主线程中执行。我不喜欢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. }); 选择器,而更喜欢直接GCD。

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

如果您正在使用主MOC,并且有任何其他线程,我强烈建议您 NOT 使用主MOC的限制并发。相反,我建议这个替代方案:

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

现在,MOC可以像其他一样使用,没有代码更改(换句话说,它在主线程上使用时仍然会像限制MOC一样正常运行)。但是,它也可以更适合地从其他线程中使用:

performBlockAndWait

如有必要,您可以调用可重入的sync(dispatch_sync 可重入 - 它会导致死锁)。您仍然必须小心任何deadly embrace操作以避免performBlockAndWait,但可以从同一线程递归调用{{1}}而不会发生死锁。

答案 2 :(得分:4)

看起来您正在两个线程之间共享NSManagedObjectContext。这只是一个猜测,但考虑到线程5上发生的崩溃,这似乎很可能就是这个问题。您只能在一个线程上使用NSManagedObjectContext。我猜你在主线程上创建mainContext,然后无论出于何种原因在后台线程上调用clear方法。

以下是一些可能的解决方案:

  1. 创建一个新的NSManagedObjectContext以在后台线程中使用。
  2. 您可以确保在主线程上调用clear方法。以下是确保这种情况发生的一种方法:

    -(void)clear:(id)object {
        if(![[NSThread currentThread] isMainThread]) {
            [self performSelectorOnMainThread:@selector(clear:) withObject:object waitUntilDone:NO];
            return;
        }
        ...
    }
    
  3. 理论上你可以lock然后unlock你的NSManagedObjectContext。这是我建议的解决方案。

  4. 阅读this thread也可能对您有帮助。