Crash on iOS9 with -[NSPersistentStoreCoordinator

2019-02-23 17:32发布

My app have recently been getting these crashes from crashlytics that is only happening on iOS9

Fatal Exception: NSInternalInconsistencyException
This NSPersistentStoreCoordinator has no persistent stores (corrupt file). It cannot perform a save operation.

The last call from the report is

-[NSPersistentStoreCoordinator _coordinator_you_never_successfully_opened_the_database_device_locked:]

and this is how the NSPersistentStoreCoordinator is created

 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

    NSURL *storeURL = [[delegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"database.sqlite"];

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

    NSError* error = nil;

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:storeURL
                                                         options:@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES} error:&error])
    {
        NSLog(@"Error adding persistent store. %@, %@", error, error.userInfo);
        return nil;
    }

    return _persistentStoreCoordinator;
}

Anybody know what could be causing these crashes?

2条回答
乱世女痞
2楼-- · 2019-02-23 18:16

I've not experienced that error on iOS9. However, you should look at your logs to see what errors you have. Is it possible you encountered your "Error adding persistent store" upon creation of the PSC?

Your method has a problem in that if you ever encounter that error, subsequent invocations will return a PSC that is neither nil, nor properly setup.

The reason is that you assign your _persistentStoreCoordinator before it is successfully setup. Thus, if there is any error, you return nil, but the next time you call that method, you will return a PSC with no stores.

In any event, you should change that method so you will only ever return either nil or a fully operational PSC.

I would change that method to something like this. Note, however, that I would never construct a core data stack like this. However, at least the following code will fix your error where you can return a partially constituted PSC.

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    NSURL *storeURL = [[delegate applicationDocumentsDirectory]
        URLByAppendingPathComponent:@"database.sqlite"];

    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
        initWithManagedObjectModel:self.managedObjectModel];
    NSError *error = nil;
    if (![psc addPersistentStoreWithType:NSSQLiteStoreType
                           configuration:nil
                                     URL:storeURL
                                 options:@{NSMigratePersistentStoresAutomaticallyOption:@YES,
                                           NSInferMappingModelAutomaticallyOption:@YES}
                                   error:&error]) {
        NSLog(@"Error adding persistent store. %@, %@", error, error.userInfo);
    } else {
        _persistentStoreCoordinator = psc;
    }

    return _persistentStoreCoordinator;
}

EDIT

Not enough room in the comments to answer your question, so I put it here.

@JodyHagins - also, you mention that you would not construct your stack like this, can you let me know what is wrong with my stack? – AWillian

I made that comment because the code you posted is eerily similar to the default Xcode core data template. You invoke the app-delegate to get the directory, indicating that this is not in the app-delegate, which is good.

However, that method indicates that you access it lazy from "anywhere" which indicates to me that your stack is not built the way I would build a stack (especially since you also include options for migration).

I didn't mean to imply that what you were doing was wrong, per se, just that's it's not how I would do it.

Now, I'll be the first to say that what I do is what I do... and I don't say it's the right way... just my way. In fact, I've not seen anyone else really do what I do (I actually subclass NSManagedObjectContext though I mind the warning and stay away from the persnickety bits). This may, in itself, indicate that what I do may not be right for you or anyone else. But, I've found it to be right for me, and the quite complex apps I've had to implement.

So, how would I build a stack?

Well, that's worthy of something much more in-depth than a SO answer, so I'll be brief. It also depends on which type of stack - parent/child, siblings with the same PSC, cousins with different PSC but same stores.

First, I don't provide separate access to the model and coordinator. You can easily access those from the context, and it usually ends up causing more problems than its worth.

Outside of tests and trivial examples, I always create my MOC asynchronously, something like this...

+ (void)createWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType
                       completion:(void(^)(NSManagedObjectContext *moc, NSError *error))completion;

The MOM is created and assigned to the PSC, the PSC is created and assigned to the MOC. It happens asynchronously so that the potentially long process of opening and initializing can be done in a background thread. The completion handler is called within a performBlock so the MOC can be used cleanly. This also prevents use before the MOC is fully setup and read to go.

Even if called with a main queue concurrency type, all the work is done in a background thread, so that only the completion is called on the main thread.

I also use the same pattern when creating related MOCs for import and temporary purposes.

查看更多
狗以群分
3楼-- · 2019-02-23 18:21

Answer from the Apple dev forum here which also explains a possible cause and a solution.

The cause is in the descriptor: Your application tried to open the open the persistent store while the device was locked, and your code tripped over the data protection API.

That's the sort of thing that you trip over on new devices and new iOS versions because the new device is fast enough to get your app running before the data protection code is done with the transition.

Solution to this kind of issue can be something like below

You pretty much have to go into your start up code, and rewrite it so that it checks to see if protected data is available, and then delaying your Core Data start up until applicationProtectedDataDidBecomeAvailable.

查看更多
登录 后发表回答