从后台恢复应用程序时出现iOS 8+核心数据错误

时间:2016-01-08 22:16:45

标签: ios objective-c core-data

此问题仅发生在iOS 8或更高版本中。我有一个核心数据管理对象,我将其分配给UIViewController的属性。每当我离开应用程序并从后台恢复它时,托管对象就会出错。每当我尝试访问对象上的属性时,错误都不会触发,所有数据都会返回nil

我为UIApplicationWillEnterForegroundNotification设置了一个观察者来检查selectedObject,并且在代码执行的那一点上,该对象还没有出现故障。它仅在应用程序进入前台后出现故障。有谁知道这里会发生什么?

编辑#1:

以下是更多相关文件。请注意,这些已经过简化,变量名称已更改以保护原始代码:

myAppDelegate.h

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

myAppDelegate.m

@synthesize managedObjectContext = __managedObjectContext;

- (NSManagedObjectContext *)managedObjectContext
{
    if (__managedObjectContext != nil)
    {
        return __managedObjectContext;
    }

    // This code only gets hit the first time the app tries to access the context.
    // After that (including when the app resumes from the background), the one stored in __managedObjectContext is returned.
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        __managedObjectContext = [[NSManagedObjectContext alloc] init];
        [__managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return __managedObjectContext;
}

DetailView.h

@property (retain, nonatomic) MyObject *selectedObject;

DetailView.m

@synthesize selectedObject;

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(willEnterForeground)
                                                 name:UIApplicationWillEnterForegroundNotification
                                               object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didBecomeActive)
                                                 name:UIApplicationDidBecomeActiveNotification
                                               object:nil];

- (void)willEnterForeground
{
    NSLog(@"App will enter foreground."); // breakpoint here
}

- (void)didBecomeActive
{
    NSLog(@"App became active."); // breakpoint here
}

- (void)viewWillAppear:(BOOL)animated
{
    NSManagedObjectContext *context = ((myAppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
    NSError *error = nil;
    NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyObject" inManagedObjectContext:context];
    NSPredicate *predicate  = [NSPredicate predicateWithFormat:@"(selected == YES)", nil];
    [request setEntity:entity];
    [request setPredicate:predicate];
    selectedObject = (MyObject*)[[context executeFetchRequest:request error:&error] objectAtIndex:0];
}

- (IBAction)buttonPressed:(id)sender
{
    NSString *link = [selectedObject link]; // breakpoint here
}

当我点击willEnterForegrounddidBecomeActive中的断点时,我会检查selectedObject

  

MyObject:0x7fa801c80bc0(entity:MyObject; id:0xd000000020940002 x-coredata:// 9775DE4D-2312-4684-904B-613302AC2B19 / MyObject / p2085; data:{link =“http://www.example.com”}

我还会检查[selectedProperty managedObjectContext][myAppDelegate managedObjectContext],两者都给我以下内容:

  

NSManagedObjectContext:0x7fa7fbc96c90

现在,如果我点击绑定到buttonPressed:的按钮并重新检查所有内容,[myAppDelegate managedObjectContext]仍会向我提供相同的输出,但[selectedObject managedObjectContext]nil且检查对象提供以下内容:

  

MyObject:0x7fa801c80bc0(entity:MyObject; id:0xd000000020940002 x-coredata:// 9775DE4D-2312-4684-904B-613302AC2B19 / MyObject / p2085; data:fault)

当访问[selectedObject link]时,它会返回nil。据我所知,当应用程序从后台恢复时,我的代码都没有按照willEnterForegrounddidBecomeActive方法运行。

3 个答案:

答案 0 :(得分:2)

managedObjectContext的{​​{1}}无法保证。如果需要,还必须存储对上下文的强引用。

如果在恢复时您的数据已过时(您无法访问存储对象的属性),请在NSManagedObject中重新获取。

答案 1 :(得分:0)

在另一个答案中阅读评论,我从OP看到了这个评论:

  

app委托不返回nil,它在相同的内存地址返回相同的上下文。只有[selectedObject managedObjectContext]返回nil。

如果这是真的,这意味着您的应用程序在某个时刻正在更新其[AppDelegate context]属性(可能会分配一个新属性,为此您应该显示您的AppDelegate代码)。 前一个上下文被释放(因为NSManagedObject没有保留自己的上下文),该对象指向一个无效的/ nil引用,因此无法触发该错误。

答案 2 :(得分:0)

问题原来是我的一些代码被无意中执行了。我的应用程序的主视图显示了MKMapView。出于某些原因,当iOS应用程序从后台恢复时,regionDidChangeAnimated事件被触发,即使该区域没有更改,此视图也未显示。

regionDidChangeAnimated运行的代码从Web服务重新加载地图数据并将其存储在核心数据中(因此我的核心数据是永久性故障的原因)。只有在通过用户交互或我明确写入的代码更改地图区域来更改地图时,才会触发此事。

我的猜测是地图正在进行一些缓存,当它从后台恢复时,它会重置导致事件无意中触发的区域。为了解决这个问题,我添加了一个检查以确保当前显示视图。

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    if (self.navigationController.visibleViewController != self)
    {
        return;
    }

    // Update map logic here...

我觉得很奇怪,当从缓存状态中检索时,不会有一些检查来防止此事件被触发,但是c' est la vie。