简体   繁体   中英

iPhone App crashes when saving changes to UIManagedObjectDocument

I have an app with two separate TableViewControllers ("TVC") that access the same CoreData SQL tables. The user calls the TVCs by pressing buttons in a parent ViewController. However, records written to CoreData by calls to an NSManagedObject Sub-class by the first TVC were not recognized by the second TVC. I figured I was creating separate instances of my UIManagedDocument, so began instantiating the UIManagedDocument in the parent VC and passing the document to the respective TVC.

Now however, when I attempt to save the document after writing records to it, the App crashes even though I am on the main thread which is where the document was originally instantiated.

Am I going about this the wrong way?

Here is the parent ViewController

//
#import "ITrackViewController.h"
#import "AthleteSearchTVController.h"

@interface ITrackViewController()

@end

@implementation ITrackViewController

@synthesize myFirstName = _myFirstName;
@synthesize myLastName  = _myLastName;

@synthesize athleteDatabase = _athleteDatabase;


-(void) setAthleteDatabase:(UIManagedDocument *)athleteDatabase
{
    if(_athleteDatabase != athleteDatabase)
    {
        _athleteDatabase = athleteDatabase;
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    if (!self.athleteDatabase) {  // for demo purposes, we'll create a default database if none is set

    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"Default Athlete Database"];
    NSLog(@"url for dataBase is %@.",url);
    // url is now "<Documents Directory>/Default Athlete Database"
    self.athleteDatabase = [[UIManagedDocument alloc] initWithFileURL:url]; // setter will create this for us on disk
    }

}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"BeginSearch"]) {
        NSLog(@"firstName is %@ and lastName is %@.",firstName.text, lastName.text);

        NSString *checkFirstName = [firstName.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
        NSString *checkLastName = [firstName.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

        if ([checkFirstName length] < 1 || [checkLastName length] < 1) {
        NSLog(@"Must include a value for both first and last names.");
        } else {
            [self dismissKeyboard:sender];
            myFirstName  = firstName.text;
            myLastName   = lastName.text;

            UIManagedDocument *sharedAthleteDataBase = [self sharedDatabase];

            NSString *searchName = [myFirstName stringByAppendingString:@"%20"];
            searchName = [searchName stringByAppendingString:myLastName];

        NSLog(@"searchName is %@ before segue.",searchName);

            [segue.destinationViewController nameToSearchFor:searchName andUIManagedDoc:sharedAthleteDataBase];
        }

    }
}

-(IBAction)dismissKeyboard:(id)sender
{
[lastName endEditing:YES];
[firstName endEditing:YES];
}

- (UIManagedDocument *) sharedDatabase
{

    __block UIManagedDocument *managedDocument = nil;

    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"AthleteData"];

    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.athleteDatabase.fileURL path]]) {
        // does not exist on disk, so create it
        [self.athleteDatabase saveToURL:self.athleteDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

            if(success){
                NSString* isMainThread;
                if ([NSThread isMainThread]) {
                    isMainThread = @"on main thread.";
                } else isMainThread = @"but not on main thread";
                NSLog(@"athleteDatabase did not exist, now created %@", isMainThread);
                managedDocument = self.athleteDatabase;
            } else {
                NSLog(@"Error creating athleteDatabase");
            }
        }];
    } else if (self.athleteDatabase.documentState == UIDocumentStateClosed) {
        // exists on disk, but we need to open it

        [self.athleteDatabase openWithCompletionHandler:^(BOOL success) {

            if(success){
                NSString* isMainThread;
                if ([NSThread isMainThread]) {
                    isMainThread = @"on main thread.";
                } else isMainThread = @"but not on main thread";
                NSLog(@"athleteDatabase was closed, is now open %@", isMainThread);
                managedDocument = self.athleteDatabase;

            } else NSLog(@"Error opening closed athleteDatabase");

        }];
    } else if (self.athleteDatabase.documentState == UIDocumentStateNormal) {
        // already open and ready to use
        managedDocument = self.athleteDatabase;
    }

    return managedDocument;
}

@end

Here is the TVC that makes calls to the NSManagedObject Sub-class to first retrieve data from an web-based API and then write it to CoreData.

- (void)fetchAthleteSearchResultsIntoDocument:(UIManagedDocument *)document
                                  whereNameIs:(NSString *)athleteName

{
    dispatch_queue_t fetchQ = dispatch_queue_create("Athlete fetcher", NULL);
    dispatch_async(fetchQ, ^{
        NSString* isMainThread;
        if ([NSThread isMainThread]) {
            isMainThread = @"on main thread.";
        } else isMainThread = @"but not on main thread";
        NSLog(@"Preparing to get records %@", isMainThread);
        NSArray *athleteRecords;
        athleteRecords = [AthleticNetDataFetcher searchForMyAthleteWithName:athleteName];

        [document.managedObjectContext performBlockAndWait:^{ // perform in the NSMOC's safe thread (main thread)
            int iCount = 0;
            for (NSDictionary *athleteInfo in athleteRecords) {

                [ResultsForAthleteSearch resultsWithAthleteInfo:athleteInfo inManagedObjectContext:document.managedObjectContext
                                           numberForSorting:iCount];
                iCount = iCount + 1;
                // table will automatically update due to NSFetchedResultsController's observing of the NSMOC
            }

            NSString* isMainThread;
            if ([NSThread isMainThread]) {
                isMainThread = @"on main thread.";
            } else isMainThread = @"but not on main thread";
            NSLog(@"Preparing to save results %@", isMainThread);

            [document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];

        }];
    });

}

The simulator crashes at the call to [document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];

The log, which is partially given below, says I am on the Main thread. I can't figure out why this should crash. It does not crash if I comment out the saveToURL call and rely on autosave.

2013-10-02 13:46:22.459 iTrackTest[14989:c07] url for dataBase is file://localhost/Users/Phlipo/Library/Application%20Support/iPhone%20Simulator/6.1/Applications/53E19DAE-6D2B-4B7F-A633-55C2BCA95AC5/Documents/Default%20Athlete%20Database/.

2013-10-02 13:46:31.031 iTrackTest[14989:c07] url for dataBase is file://localhost/Users/Phlipo/Library/Application%20Support/iPhone%20Simulator/6.1/Applications/53E19DAE-6D2B-4B7F-A633-55C2BCA95AC5/Documents/Default%20Athlete%20Database/.

2013-10-02 13:46:31.039 iTrackTest[14989:61f] Preparing to get records but not on main thread

2013-10-02 13:46:31.065 iTrackTest[14989:c07] athleteDatabase was closed, is now open on main thread.
2013-10-02 13:46:31.620 iTrackTest[14989:61f] [AthleticNetDataFetcher executeSearchRequest:] received {
.
[A bunch of data in JSON format].
.
}


2013-10-02 13:46:31.633 iTrackTest[14989:c07] Preparing to save results on main thread.
2013-10-02 13:47:23.258 iTrackTest[16150:3f07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores.  It cannot perform a save operation.'

I did not encounter this NSPErsistence error when I instantiated the MOD in the TVC. Do I need a more explicit DataBase Helper, perhaps?

Thanks in advance for any help.

Got pulled off this for a bit, but here is what I figured out.

I remembered that in Android I had used a SQL helper classes. Paul Hegarty recommends this approach, with some caveats, for simple applications.

Tim Roadley's tutorial is a great resource on this topic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM