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

And*_*rei 5 macos exc-bad-access core-data objective-c

在我的应用程序中,我正在删除(或尝试删除)来自两个核心数据存储的所有记录,然后再添加新的记录.它们是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;
}

Run Code Online (Sandbox Code Playgroud)

该应用程序似乎总是围绕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
Run Code Online (Sandbox Code Playgroud)

其他信息

我用过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];
Run Code Online (Sandbox Code Playgroud)

这是mainSLSyncOperation 的方法(继承自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];
}
Run Code Online (Sandbox Code Playgroud)

正在使用的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;
}
Run Code Online (Sandbox Code Playgroud)

在此之后,我正在使用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;
}

Run Code Online (Sandbox Code Playgroud)

fetchObjectsForEntityName方法取自这里.

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

更多信息

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

Res*_*h32 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];
}
Run Code Online (Sandbox Code Playgroud)


Jod*_*ins 4

首先,你说实体之间没有关系,但是它们之间有什么关系呢?

其次,由于 NULL 指针被取消引用,您会遇到分段错误。

第三,您有多少个 NSManagedObjectContext 对象,以及如何访问它们?

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

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

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

编辑

不幸的是,您没有展示如何调用您的clear方法。我敢打赌它就在您用评论替换的内容中:

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

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

mainContext是 AppDelegate 拥有的 MOC,用于主线程中的内容。它也在这个“其他”线程中被调用和使用。请参阅您的堆栈跟踪以获取证据。这是核心数据和线程的第一条规则。您不得允许多个线程并发访问同一 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.
});
Run Code Online (Sandbox Code Playgroud)

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

managedObjectContext = [[NSManagedObjectContext alloc]
                        initWithConcurrencyType:NSMainQueueCurrencyType];
Run Code Online (Sandbox Code Playgroud)

现在,该 MOC 可以像另一个 MOC 一样使用,无需更改代码(换句话说,在主线程上使用时,它仍然会像限制 MOC 一样正常工作)。然而,它也可以更合适地从其他线程使用:

[managedObjectContext performBlock:^{
    // Do anything with this MOC because its protected, and
    // running on the right thread.
}];
Run Code Online (Sandbox Code Playgroud)

如果有必要,你可以调用performBlockAndWaitwhich是可重入的(dispatch_sync是不可重入的——它会导致死锁)。您仍然必须小心任何操作,以sync避免从同一个线程递归调用但不会出现死锁。deadly embraceperformBlockAndWait