How to test if your app needs an upgrade

Screen Shot 2014-07-21 at 09.37.32

Sometimes you want to implement a new feature in your latest release which relies on an existing feature to be amended in some way. For example, you’ve added iCloud functionality to your app and now you need to upgrade the store file.

It’s important that such internal upgrade routines only run once when necessary and not every time your user launches the app. The simplest way to do this is to set a user default value after the migration, and test if it exists on every run of your app. If it does, the migration is not necessary. If it does not, we haven’t upgraded the app yet.

Here’s a quick example of how you can implement this. I’m doing this in the AppDelegate’s didFinishLaunchingWithOptions method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // check if an upgrade is necessary
    if (![[NSUserDefaults standardUserDefaults]boolForKey:@"didWeUpgrade"]) {
        
        // let's upgrade
        [self upgradeApp];
        
        // and remember for next time
        [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"didWeUpgrade"];
        [[NSUserDefaults standardUserDefaults]synchronize];
    }
    
    return YES;
}

#pragma mark - Upgrade Checks

- (void)upgradeApp {
    
    // add your upgrade code here
    NSLog(@"Just upgraded your app. On the next run, this does not happen again.");
}

Be wise with the choice of your naming conventions for BOOLs and methods and make a note of which version of your app needed this upgrade. For example, add something like “upgraded Core Data with new string values as of version 1.3”.

I’m saying this because you may need to upgrade again with another feature in the future, and you don’t want to confuse yourself, or run the old upgrade routine again in error. Just thought I’d mention it – you’ll thank me later 😉

Here’s a quick demo project on GitHub:





5 thoughts on “How to test if your app needs an upgrade

  1. Thanks Jay for creating this and this is really perfect! 🙂 I’m so sorry for yet another question.. I know I’m so close now!..

    So in my scenario:

    App Store version with data (no iCloud)
    Update to iCloud version
    Data migrates to iCloud
    Other devices pick up data *
    Run the app again and it doesn’t use the new “migrated” store **

    Starting with **, when I run the app again, because the user defaults not is true (i.e. it exists), it doesn’t go to the upgrade method, which makes sense, but the logging in the console now doesn’t reflect iCloud usage at all and it looks like it’s reverting back to using the local storage. So my question is, when I migrate to use the iCloud store the first time the app is run, how do I set it so that the app always uses that store instead of reverting back to the local store when iCloud is available, and the account is the same?

    I’ll ask this separately without wanting to confuse too many things!

    Thanks again Jay!

    1. No problem at all! Without seeing your code, my hunch is that the puzzle lies in the custom initialiser for the persistent store coordinator. That’s where you setup the store file, and that’s where you’ll either use iCloud with Core Data or not. Therefore, you need to find a way to “fork” the initialiser with a similar if/then statement as in the example above.

      So if the app has been upgraded, and now runs again, the initialiser needs to setup the persistent store coordinator with iCloud. If not, it needs to be set as it always has been.

      The easiest way to do this is to setup the options dictionary differently. Something like this:

      - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
      {
          if (_persistentStoreCoordinator != nil) {
              return _persistentStoreCoordinator;
          }
          
          NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Upgrade.sqlite"];
          NSError *error = nil;
          
          // get a reference to your iCloud URL
          NSURL *cloudURL = [self grabCloudPath:@"iCloud"];
          
          // setup options depending on migration
          NSDictionary *options = nil;
          if ([[NSUserDefaults standardUserDefaults]boolForKey:@"didWeUpgrade"]) {
              options = @{NSPersistentStoreUbiquitousContentURLKey: cloudURL};
          }
          
          _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
          if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
      
              NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
              abort();
          }    
          
          return _persistentStoreCoordinator;
      }
      

      Let me know if that works for you (and if it makes sense).

  2. Thanks once again Jay! An amazing help. I thought after I wrote it that it’s a bit hard without code and I was putting something together when your answer came through and that worked like a charm. The forked if-else statement worked perfectly in the persistentStoreCoordinator, like you have in the snippet you posted.

    So the if is evaluating if the userDefaults does exist and it contains the iCloud options and else evaluates without userDefaults and it contains the non-icloud options. It works like a charm because when the second device is connected in, it shows there’s no user defaults and it migrates the data successfully, but the next time it’s run, it works like a charm showing that userDefaults does exist and therefore to use the iCloud options.

    Honestly, you’ve managed to in one day, help me out with issues I’ve been having for quite some time!

    Thanks so much for this – I really appreciate it 🙂

    1. I’m so glad it helped you! I know what it’s like – these things can drive you crazy. And I honestly thing that Apple could have made it all a little less complicated… Good luck with the app!

Leave a Reply