iOS 5核心数据冻结

26

我尝试做一件简单的事情:

NSArray * entities = [context executeFetchRequest:inFetchRequest error:&fetchError];

没有什么花哨的东西。但这在iOS 5上会冻结,而在iOS 4上则正常工作。我没有收到任何异常、警告或错误信息;我的应用程序只是简单地冻结了。

请帮助我!我快要崩溃了! ;)


它是冻结还是崩溃?它不能两者兼备。如果它崩溃了,就会有一个崩溃日志或堆栈跟踪。 - Stephen Darlington
1
你没有在线程之间共享上下文,对吧? - Mark Granoff
@Dries - 我也在调查这个问题,因为我发现了相同的行为。如果你在4.3模拟器上运行,它是否正常工作(我的是)?在5.0模拟器上,它经常会无故锁定,没有错误消息或调试信息。 - ferdil
这绝对是关于iOS5处理并发的问题 - 我正在仔细检查我的代码,它看起来不太好。 - ferdil
我遇到了同样的问题,但我的_objectStoreLockCount是1... - olivaresF
显示剩余2条评论
7个回答

19

我不知道您是否也使用了不同的线程。如果是的话,问题在于NSManagedObjects本身不是线程安全的。在主线程上创建ManagedContext并在另一个线程上使用会导致该线程挂起。

也许这篇文章可以帮您:http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/

Apple有一个演示应用程序,可处理多个线程(通常是主线程和后台线程)上的Coredata:http://developer.apple.com/library/ios/#samplecode/TopSongs/Introduction/Intro.html

我的解决方法如下:

  • 在应用程序委托中:创建持久存储(所有线程都使用同一个),并为主线程创建Coredata管理的上下文,
  • 在后台线程中,创建一个新的管理上下文(来自同一持久存储),
  • 保存时使用通知,以便让主Context知道后台线程何时完成(插入行或其他操作)。

有几种解决方案,例如使用NSQueueOperation。对于我的情况,我正在使用while循环。如果可能有所帮助,请参考我的代码。但是,苹果的并发文档和他们的Top Songs示例应用程序是开始的好地方。

在应用程序委托中:

 -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    self.cdw = [[CoreDataWrapper alloc] initWithPersistentStoreCoordinator:[self persistentStoreCoordinator] andDelegate:self];
    remoteSync = [RemoteSync sharedInstance];

    ...

    [self.window addSubview:navCtrl.view];
    [viewController release];

    [self.window makeKeyAndVisible];
    return YES; 
}    

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (persistentStoreCoordinator == nil) {
        NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
        NSLog(@"Core Data store path = \"%@\"", [storeUrl path]);
        persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
        NSError *error = nil;
        NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
        NSAssert3(persistentStore != nil, @"Unhandled error adding persistent store in %s at line %d: %@", __FUNCTION__, __LINE__, [error localizedDescription]);
    }
    return persistentStoreCoordinator;
}


-(NSManagedObjectContext *)managedObjectContext {
    if (managedObjectContext == nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    }
    return managedObjectContext;
}

-(NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (persistentStoreCoordinator == nil) {
        NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
        NSLog(@"Core Data store path = \"%@\"", [storeUrl path]);
        persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
        NSError *error = nil;
        NSPersistentStore *persistentStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
        NSAssert3(persistentStore != nil, @"Unhandled error adding persistent store in %s at line %d: %@", __FUNCTION__, __LINE__, [error localizedDescription]);
    }
    return persistentStoreCoordinator;
}


-(NSManagedObjectContext *)managedObjectContext {
    if (managedObjectContext == nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    }
    return managedObjectContext;
}


-(NSString *)persistentStorePath {
    if (persistentStorePath == nil) {
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths lastObject];
        persistentStorePath = [[documentsDirectory stringByAppendingPathComponent:@"mgobase.sqlite"] retain];
    }
    return persistentStorePath;
}

-(void)importerDidSave:(NSNotification *)saveNotification {
    if ([NSThread isMainThread]) {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
    } else {
        [self performSelectorOnMainThread:@selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
    }
}

在运行后台线程的对象中:

monitor = [[NSThread  alloc] initWithTarget:self selector:@selector(keepMonitoring) object:nil];

-(void)keepMonitoring{
    while(![[NSThread currentThread]  isCancelled]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];    
        AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

        //creating the cdw here will create also a new managedContext on this particular thread
        cdwBackground = [[CoreDataWrapper alloc] initWithPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator andDelegate:appDelegate];
        ...
    }
}

希望这能帮到你。

M.


11

感谢这个页面上提供的提示,教我如何解决从iOS4升级后出现的这个冻结问题。自从我开始在iOS上编程以来,这一直是我遇到的最令人恼火的问题。

对于只有少数调用其他线程中的上下文的情况,我找到了一个快速的解决方案。

我只是使用performSelectorOnMainThread

    [self performSelectorOnMainThread:@selector(stateChangeOnMainThread:) withObject: [NSDictionary dictionaryWithObjectsAndKeys:state, @"state", nil] waitUntilDone:YES];

如果想要检测上下文是从另一个线程中调用的位置,可以在调用上下文的函数上设置NSLog断点,就像以下代码片段中一样,并在这些函数上使用performSelectorOnMainThread

if(![NSThread isMainThread]){
     NSLog(@"Not the main thread...");
}

我希望这可能有所帮助...


我也遇到了同样的冻结问题,但我肯定没有使用线程,所以调用performSelectorOnMainThread也没有改变任何东西。 - A. Z.
也许这是一个不同的问题。如果应用程序冻结时暂停调试器,您将看到它停止的位置。如果您可以从与Thread 1不同的线程访问CoreDataModel上下文,则是相同的问题,您可以通过以下方式解决它。否则,您可能需要进一步搜索问题以识别和解决它。 - Carles Estevadeordal

6
我遇到了同样的问题。如果您在调试器下运行程序并且应用程序“挂起”,请停止应用程序(使用调试器上的“暂停”按钮)。如果您位于executeFetchRequest行,则检查上下文变量。如果它具有ivar _objectStoreLockCount,并且大于1,则正在等待关联存储上的锁定。
某处,您正在创建与关联存储的竞争条件。

4
这似乎是尝试从创建它的线程/队列以外的线程/队列访问NSManagedObjectContext。正如其他人建议的那样,您需要检查线程并确保遵循Core Data的规则。

3

执行获取请求必须发生在创建上下文的线程中。

请记住,这不是线程安全的,尝试从另一个线程执行executeFetchRequest将导致不可预测的行为。

为了正确执行此操作,请使用

[context performBlock: ^{
     NSArray * entities = [context executeFetchRequest:inFetchRequest error:&fetchError];   
}];

这将在与上下文相同的线程中执行 executeFetchRequest,可能是主线程,也可能不是。


0

使用fetchrequest删除所有对象对我无效,sqlite看起来已经损坏。我找到的唯一方法是

    //Erase the persistent store from coordinator and also file manager.
    NSPersistentStore *store = [self.persistentStoreCoordinator.persistentStores lastObject];
    NSError *error = nil;
    NSURL *storeURL = store.URL;
    [self.persistentStoreCoordinator removePersistentStore:store error:&error];
    [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];

    //Make new persistent store for future saves   (Taken From Above Answer)
    if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        // do something with the error
    }

0
在我的情况下,应用程序在没有任何警告的情况下在“executeFetchRequest”之前会冻结。解决方案是将所有数据库操作包装在@synchronized(persistentStore)中。例如:
NSArray *objects;
@synchronized([self persistentStoreCoordinator]) {
            objects = [moc executeFetchRequest:request error:&error];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接