Resolved Core Data not saving to persistent store

Discussion in 'iOS Programming' started by moonman239, Dec 20, 2013.

  1. moonman239, Dec 20, 2013
    Last edited: Jan 6, 2014

    moonman239 macrumors 68000

    Joined:
    Mar 27, 2009
    #1
    I have a problem. In one part of the app, I have code which is supposed to make changes to an NSManagedObject, then call the save method of a context. I thought this was enough to save the changes to the managed object, but I guess not.

    The app seems to be saving the data to my iPad's RAM, but not to the SQLite store it's supposed to be saving the data to. Am I missing anything?

    EDIT: I should add that I did not see any errors, and that I copied the Core Data code from a project that includes it.
     
  2. Sonnestah macrumors regular

    Joined:
    Mar 2, 2013
    #2
    Yes, you did not post any code.

    Why do you never post any code?
     
  3. moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #3
    Below is some code from my app. I have excluded RootViewController.h, RootViewController.m and MainViewController.h because they do not contain any relevant code. Everything I've decided to hide has been replaced with the word "hidden" or with "// Hidden code"
    AppDelegate.h
    Code:
    
    //
    //  AppDelegate.h
    //
    //  Created by Montana on 7/17/13.
    //  Copyright (c) 2013 Montana. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    
    @interface AppDelegate : UIResponder <UIApplicationDelegate>
    
    @property (strong, nonatomic) UIWindow *window;
    @property(readonly,strong,nonatomic)NSManagedObjectContext *managedObjectContext;
    @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
    @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
    - (void)saveContext;
    - (NSURL *)applicationDocumentsDirectory;
    @end
    
    
    AppDelegate.m:
    Code:
    #import "AppDelegate.h"
    #import "RootViewController.h"
    #import <SpeechKit/SpeechKit.h>
    
    @implementation AppDelegate
    
    const unsigned char SpeechKitApplicationKey[] = {0xa5, 0x50, 0x4e, 0xc5, 0x6a, 0x78, 0xd0, 0x52, 0x80, 0xd3, 0xdd, 0x68, 0xe3, 0xd7, 0x42, 0xc5, 0x99, 0x5c, 0x43, 0xa3, 0x59, 0xec, 0x86, 0x64, 0x57, 0xcd, 0x24, 0x2a, 0x90, 0xf6, 0xbf, 0x36, 0x47, 0x0d, 0xfb, 0xb1, 0x85, 0xcb, 0x03, 0x05, 0x9f, 0xe0, 0x6d, 0x82, 0xb7, 0xaf, 0x48, 0x51, 0x0c, 0x8b, 0xf4, 0x65, 0x77, 0x7a, 0xc9, 0xcf, 0x47, 0xed, 0xf9, 0xd8, 0xe3, 0xb8, 0x0e, 0x2a};
    
    @synthesize managedObjectContext = _managedObjectContext;
    @synthesize managedObjectModel = _managedObjectModel;
    @synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // Override point for customization after application launch.
        RootViewController *rootViewController = (RootViewController *)self.window.rootViewController;
        rootViewController.managedObjectContext = self.managedObjectContext;
        return YES;
    }
    - (void)applicationWillResignActive:(UIApplication *)application
    {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }
    
    - (void)applicationDidEnterBackground:(UIApplication *)application
    {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }
    
    - (void)applicationWillEnterForeground:(UIApplication *)application
    {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }
    
    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }
    
    - (void)applicationWillTerminate:(UIApplication *)application
    {
        // Saves changes in the application's managed object context before the application terminates.
        [self saveContext];
    }
    
    - (void)saveContext
    {
        NSError *error = nil;
        NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
        if (managedObjectContext != nil) {
            if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
                // Replace this implementation with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }
    }
    
    #pragma mark - Core Data stack
    
    // Returns the managed object context for the application.
    // If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
    - (NSManagedObjectContext *)managedObjectContext
    {
        if (_managedObjectContext != nil) {
            return _managedObjectContext;
        }
        
        NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
        if (coordinator != nil) {
            _managedObjectContext = [[NSManagedObjectContext alloc] init];
            [_managedObjectContext setPersistentStoreCoordinator:coordinator];
        }
        return _managedObjectContext;
    }
    
    // Returns the managed object model for the application.
    // If the model doesn't already exist, it is created from the application's model.
    - (NSManagedObjectModel *)managedObjectModel
    {
        if (_managedObjectModel != nil) {
            return _managedObjectModel;
        }
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"core_data" withExtension:@"momd"];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
        return _managedObjectModel;
    }
    
    // Returns the persistent store coordinator for the application.
    // If the coordinator doesn't already exist, it is created and the application's store added to it.
    - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
    {
        if (_persistentStoreCoordinator != nil) {
            return _persistentStoreCoordinator;
        }
        
        NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"core_data.sqlite"];
        
        NSError *error = nil;
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
        if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
            /*
             Replace this implementation with code to handle the error appropriately.
             
             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             
             Typical reasons for an error here include:
             * The persistent store is not accessible;
             * The schema for the persistent store is incompatible with current managed object model.
             Check the error message to determine what the actual problem was.
             
             
             If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
             
             If you encounter schema incompatibility errors during development, you can reduce their frequency by:
             * Simply deleting the existing store:
             [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
             
             * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
             @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
             
             Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
             
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
        
        return _persistentStoreCoordinator;
    }
    
    #pragma mark - Application's Documents directory
    
    // Returns the URL to the application's Documents directory.
    - (NSURL *)applicationDocumentsDirectory
    {
        return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    }
    
    @end
    
    RootViewController.m contains no significant code, so I will not post its code.

    prepareForSegue method in MainViewController.m:
    Code:
    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        RootViewController *rootViewController = (RootViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
        [[segue destinationViewController] hidden].managedObjectContext = [rootViewController managedObjectContext];
        
    }
    
    viewLoaded and touchesEnded methods in destination view controller's parent class's .m file:
    Code:
    
    - (void)viewLoaded
    {
        // Hidden code
        [[self hidden] getManagedObject];
        managedObject = self.hidden.managedObject;
        if ([[managedObject valueForKey:@"individualhidden"] doubleValue] > 0)
        {
            thecount = [[managedObject valueForKey:@"individualhidden"] doubleValue] * [[self words] count];
        }
        [self hidden];
        
    }
    
    - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
            // Hidden code
                if (hidden <= 60)
                {
                    NSString *hidden = [selectedLabel.text stringByAppendingString:self.lastLetters.text];
                    if ([letterGroup isEqualToString:correctWord])
                    {
                        selectedLabel.text = @"";
                        self.hidden.text = hidden;
                        thecount = thecount + 1;
                        NSInteger score = (thecount / [[self hidden] count]);
                        [managedObject setValue:[NSNumber numberWithInteger:score] forKey:@"hidden"];
                        NSError *error;
                        if ([[[self hidden] managedObjectContext] save:&error])
                        {
                            NSLog(@"%@",[error localizedDescription]);
                            abort();
                        }
                        [self hidden];
                    }
                }
        selectedLabel = nil;
    }
    
    The managed object is stored as a property of a property of the destination view controller. It is generated using the following code. Each implementation is in its own .m file.

    Code:
    @implementation hidden
    -(void)getManagedObject
    {
        self.managedObject = [NSManagedObject managedObjectForhidden:[self name] context:[self managedObjectContext]];
    }
    @end
    
    @implementation NSManagedObject (FamilyManagedObjectCategory)
    + (NSManagedObject *)managedObjectForhidden:(NSString *)wordFamily context:(NSManagedObjectContext *)context
    {
        NSManagedObject *managedObject;
        UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
        NSString *predicateFormat = [@[@"familyName LIKE '",hidden,@"'"] componentsJoinedByString:@""];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat];
        NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"hidden" inManagedObjectContext:context];
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setPredicate:predicate];
        [fetchRequest setEntity:entityDescription];
        NSError *error = [[NSError alloc] init];
        NSArray *managedObjectArray = [context executeFetchRequest:fetchRequest error:&error];
        if (managedObjectArray != nil)
        {
            if ([managedObjectArray count] == 0)
            {
            managedObject = [[NSManagedObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:context];
                [managedObject setValue:hidden forKey:@"familyName"];
                if (![context save:&error]) {
                    NSLog(@"%@",[error localizedDescription]);
                }
                }
            else
            {
                managedObject = [managedObjectArray firstObject];
            }
            }
        else
        {
            NSLog(@"%@",[error localizedDescription]);
        }
        return managedObject;
    }
    @end
    
     
  4. namanhams macrumors regular

    Joined:
    Jun 3, 2009
    #4
    When you save CoreData, you call this :

    Code:
    NSError *error = nil;  // (1)
    [yourManagedObjectContext save:&error];  // (2)
    
    Make sure that 'yourManagedObjectContext' is not nil, and that (2) returns true. If not, definitely 'error' will contain the information about the issue.
     
  5. crossi81 macrumors member

    Joined:
    Aug 13, 2012
    #5
    Try

    Code:
    managedObject = [NSEntityDescription insertNewObjectForEntityForName:@"hidden" inManagedObjectContext:context];
    And build "context" this way:

    (remember to import AppDelegate.h)

    instead of

     
  6. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #6
    We can't help you troubleshoot code that isn't real code. As posted, your code makes no sense. The word "hidden" is used as a method name, instance variable name, property name, and I don't know what else, and those different uses conflict. In your touchesEnded method, you only update your managed object context if hidden <= 60. Then you replace hidden with a string.

    You said you replaced various things with hidden, but as a result, you've hidden the code that we need to see.


     
  7. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #7
    I'm guessing they're trying to show their code without revealing it's true purpose and avoiding having someone steal that idea and produce their own app. In that case, I would suggest creating a test app to "demonstrate" to us the problem issue. If it doesn't recreate the issue, than perhaps the issue lies with other code.
     
  8. moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #8
    Exactly.

    I would like to thank everyone for helping me out here. Programs are hard to debug when the code looks good.
     
  9. crossi81 macrumors member

    Joined:
    Aug 13, 2012
    #9
    News? Did my suggestion work?
     
  10. moonman239, Dec 30, 2013
    Last edited: Dec 30, 2013

    moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #10
    The context is not nil, but my view controllers are referring to a nil context. This appears to be because mainViewController is referencing a nil property of one of my view controllers.

    Update: I just edited mainViewController.m so that the view controller sets the aforementioned property to a new instance of that property's subclass.

    Update #2: The program still will not save the data.
     
  11. moonman239, Jan 6, 2014
    Last edited: Jan 6, 2014

    moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #11
    Update #3: It appears that the problem lies with a variable that's supposed to be updated every time the user increases his or her score.

    Every time the user makes a correct choice, the value of an integer named "thecount" increases by 1. The app is supposed to store the current score, which equals the number of questions answered correctly divided by the number of questions that either have been or will be presented, in a double named "score".

    Despite the fact that thecount's value increases, score's value stays the same.

    EDIT: I decided that I was telling the app to perform integer division. Thanks to the people who answered this Java question.
     

Share This Page