5

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

这是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];
}

正在使用的 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 指针。

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

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

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

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

编辑

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

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

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

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

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

现在,您有几个选择:

将本地生成的 MOC 传递给这些方法,以便它们在正确的 MOC 上运行。也许您打算改为对 MOC 进行快照?

如果你真的打算让这些方法在mainContextMOC 上运行,那么你需要确保它们在主线程中执行。我不喜欢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.
}];

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

于 2012-09-05T15:06:30.957 回答
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。这是我最不推荐的解决方案。

Reading this thread might also help you.

于 2012-09-05T18:11:34.437 回答