Apr 242013
 

A recent StackOverflow question got me thinking about the best way to manage a Core Data Migration. This post won’t address how to map your model or do a lightweight migration; rather, it will focus on how and where to launch the migration process.

The question on StackOverflow asked how to manage a migration that takes an indeterminate amount of time to complete. Most developers would consider putting the migration code into the -application:didFinishLaunchingWithOptions: method since one would want the migration to take place before doing anything else. This has two problems (as outlined by the stackoverflow responses):

  • executing a migration on the main thread blocks the thread and prevents feedback to the user of what’s happening; and
  • a migration that takes too long will cause the iOS watchdog to terminate the app.

The immediate thinking might be to put the migration on a background thread. However, as the accepted answer pointed out, this presents a problem if any other process tries to access Core Data while the migration is taking place. This could easily happen elsewhere in the -application:didFinishLaunchingWithOptions: method or in the rootViewController.

So what to do?

I found a solution to balance these concerns by:

  • running the migration in a background thread;
  • giving the user visual feedback that an upgrade is taking place; and
  • not running anything else until the migration completes.

The idea is to postpone the initialization of the app and the assignment of the rootViewController until the migration has had a chance to do its thing. Here’s an outline of the approach:

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Setup the window, but don't add the rootViewController yet
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window makeKeyAndVisible];

    // -doesRequireMigration is a method in my RHManagedObject Core Data framework. Function should be self explanatory.
    if ([MyManagedObject doesRequireMigration]) {
        // SVProgressHUD displays an animated spinner
        [SVProgressHUD showWithStatus:NSLocalizedString(@"Upgrading...", nil) maskType:SVProgressHUDMaskTypeGradient];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

            // do your migration here in a background thread

            dispatch_async(dispatch_get_main_queue(), ^{
                [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"Success!",nil)];
                [self postApplication:application didFinishLaunchingWithOptions:launchOptions];
            });
        });

    } else {
        [self postApplication:application didFinishLaunchingWithOptions:launchOptions];
    }

    return YES;
}

// this method contains all the initialization code normally found in -application:didFinishLaunchingWithOptions:
-(void)postApplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // add the rootViewController here
    self.window.rootViewController = ….

    // anything else you want to run at launch
}

For those using RHManagedObject, the upgrade can be performed by simply accessing the managed object context for that thread. In other words, replace “// do your migration here in a background thread” with:

[YourManagedObjectSubclass managedObjectContextForCurrentThread];

This assumes your model has been correctly versioned, etc.

  31 Responses to “Core Data Migration”

  1. Hi Chris,

    I am using your model and love it. Check our app out – Pocket WOD. I do understand CoreData, but it would be helpful if you could show me how to do a lightweight migration using the above code in the context of RHManagedObject?

    Thanks again for such a great framework.

  2. to be more specific – how can I adapt this lightweight migration code to work with RH?

    – (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (persistentStoreCoordinator_ != nil) {
    return persistentStoreCoordinator_;
    }
    NSString *storePath = [AppDelegate_Shared coredataDatabasePath];
    NSURL *storeURL = [NSURL fileURLWithPath:storePath];

    // important part starts here
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
    nil];
    NSError *error = nil;
    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
    // and ends here

    LogError(@”Unresolved error %@, %@”, error, [error userInfo]);
    // Do something
    }
    return persistentStoreCoordinator_;
    }

  3. Hi Tyler, thanks for your comment. It’s actually very easy to do a lightweight migration with RHManagedObject. You simply need to access the managed object context for the existing thread on the line that reads “// do your migration here in a background thread”. In other words, replace that line with:

    [YourManagedObjectSubclass managedObjectContextForCurrentThread];

    That’s it! I hope this helps and glad you find the library useful!

  4. I tried many times get error on below.

    Please help me to step to migrate core data.

    I add new attributes field without clean delete how to successfully run…

    Please tell me steps.. Thanks in advanced..

    Email the procedure…..

    NSString *storePath = [AppDelegate_Shared coredataDatabasePath]; **

    ** in this line what does AppDelegate_Shared coredataDatabasePath this mean?

    How to declare….

    Unresolved error Error Domain=NSCocoaErrorDomain Code=134130 “The operation couldn’t be completed. (Cocoa error 134130.)” UserInfo=0x819a7e0 {URL=file://localhost/Users/admin/Library/Application%20Support/iPhone%20Simulator/6.0/Applications/40B69C9D-D2FF-4245-84A8-ACE5B38C562F/Documents/PassingThoughts.sqlite, metadata={
    NSPersistenceFrameworkVersion = 419;
    NSStoreModelVersionHashes = {
    Thought = ;
    };

    • You might consider looking at Apple’s Documentation on Lightweight Migrations.

      • How to use clone method? Is it possible to add new attributes and clone method clone ?
        If yes then how?

        • I regret I don’t understand your question.

          • – (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
            if (persistentStoreCoordinator_ != nil) {
            return persistentStoreCoordinator_;
            }
            NSString *storePath = [AppDelegate_Shared coredataDatabasePath];
            NSURL *storeURL = [NSURL fileURLWithPath:storePath];

            // important part starts here
            NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
            [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
            [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
            nil];
            NSError *error = nil;
            persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
            if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
            // and ends here

            LogError(@”Unresolved error %@, %@”, error, [error userInfo]);
            // Do something
            }
            return persistentStoreCoordinator_;
            }

            I have used above code and my app not crash but when my data model is changed it throws error on commit method like this

            Like [Employee commit];

            2013-09-20 13:22:53.017 coreDataWithMethod[1105:c07] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.’
            *** First throw call stack:
            (0x15cf012 0x13f4e7e 0x43222 0x7133c 0xaa29 0x7bd3 0xd18b 0x1408705 0x33f920 0x33f8b8 0x400671 0x400bcf 0x3ffd38 0x36f33f 0x36f552 0x34d3aa 0x33ecf8 0x241cdf9 0x241cad0 0x1544bf5 0x1544962 0x1575bb6 0x1574f44 0x1574e1b 0x241b7e3 0x241b668 0x33c65c 0x230d 0x2235 0x1)
            libc++abi.dylib: terminate called throwing an exception

          • RHManagedObject will perform a lightweight migration the first time you access a data store that requires it. That means the line that reads: “// do your migration here in a background thread” can be replaced with something like [YourManagedObject managedObjectContextForCurrentThread];.

  5. How to manage relationship between two entities.. Can you see some sample code ? OR explain …
    I have two entities
    1) student entities in that two fields rollno,name
    2) studentDetails in that one field address.

    • How to deal with relationship?
      Can you explain or I paste code?

        • i understand this.. But problem is that I dont understand what to write in code..

          I create student and details entities

          and map relation to it.

          my question is that

          But how to add and fetch data????

          • Look inside the generated .h file. There should be methods to add objects into the relationships. Fetching is just done by accessing the getters for the relationship.

          • is it compulsory to generate ?

          • You must always generate the NSManagedObject subclasses when your object graph changes.

          • but i dont understand how to write adding object code.. any sample code to understand?

          • The code for adding relationships is generated by the model definition as long as you have those relationships in your model. I regret I don’t have any code samples for this, but you should have no problem finding it online. It’s standard stuff.

          • I understood concept.

            But in that i have
            1)

            Employee.h
            Employee.m

            2)
            EmployeeEntity.h
            EmployeeEntity.m

            so which one generate to NSManagedObject subclasses 1 or 2 ?

          • It depends on your model definition and if you have an inverse relationship between your child and parent. Either way, just generate both.

          • Yes that thing i understood…

            But suppose Customer CustomerDetails is there.

            So in that both

            Like
            Customer .h
            Customer .m
            CustomerModel.h
            CustomerModel.m

            2)
            CustomerDetail.h
            CustomerDetail.m
            CustomerDetailModel.h
            CustomerDetailModel.m

            In 1 file inheritance

            @interface CustomerModel: RHManagedObject

            @interface Customer: CustomerModel

            so which one file is generate to NSManaged subclasses?

        • I regret I don’t understand your question.

          • import <Foundation/Foundation.h>

            import <CoreData/CoreData.h>

            @class StudentDetail;

            @interface StudentData : NSManagedObject

            @property (nonatomic, retain) NSString * name;
            @property (nonatomic, retain) NSString * rollno;
            @property (nonatomic, retain) NSSet *studentdetails;
            @end

            @interface StudentData (CoreDataGeneratedAccessors)

            (void)addStudentdetailsObject:(StudentDetail *)value;
            (void)removeStudentdetailsObject:(StudentDetail *)value;
            (void)addStudentdetails:(NSSet *)values;
            (void)removeStudentdetails:(NSSet *)values;

            @end

            generated file.

            But how access below method…..
            +(id)newEntity {
            return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:[self managedObjectContextForCurrentThread]];
            }

          • That thing i also solved.. but one problem is there..
            in my student detail @interface StudentDetail : RHManagedObject

            @property (nonatomic, retain) NSString * rollno;
            @property (nonatomic, retain) NSString * address;
            @property (nonatomic, retain) NSNumber * cellnumber;
            @property (nonatomic, retain) StudentData *student;
            @property (nonatomic, retain) RHManagedObject *studentinfo;

            ** in this RHManagedObject or NSManagedObject is writing?

            in relationship coding
            stud is Student class object
            stud_Det is StudentDetail class object

            [stud addStudentdetailsObject:stud_Det];
            [stud_Det setStudentinfo:stud];

            ** this line throws error

          • i used inverse relationship

  6. Where to write icloud set up sync code in your RHmanaged framework ?

  7. I follow this tutorial http://timroadley.com/2012/04/03/core-data-in-icloud/

    So you can tell me on where changes can be made in your framework ?

    Thanks in advance..

    I dont understand That code tells the Managed Object Context to post notifications when iCloud updates data. To use those notifications add the following method to RolesTVC.m and PersonsTVC.m:
    these thing

  8. Please tell me How error parameter works?

 Leave a Reply

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)