Docset Viewer v1.2 and how to customize iCloud backups

I’ve recently released version 1.2 of Docset Viewer, which fixes a number of bugs people experienced with the previous version. If you had problems with the previous release, please give this one a try.

One of the improvements I’ve added is the ability to customize whether or not you would like to back up your Docsets (which can get quite large) into iCloud. To keep with the instructional nature of this site, I’ll show you how you can do that in your own apps.

MobileMe/iCloud backup basics

iCloud replaces the former MobileMe backup mechanism for syncing files for backup.  In both MobileMe and iCloud, you use the -[NSURL setResourceValue:forKey:error:] method to set a boolean attribute indicating whether or not an individual file should be excluded from backups.  By default all files will be backed up.

In Docset Viewer, since the files can take up hundreds of megabytes, all of which are often times easily downloaded from the Internet, it doesn’t make sense to back them all up and use all your precious iCloud capacity.

The only problem is that the new iCloud backup resource value was introduced in iOS 5.1, and replaces the former MobileMe tag.  Furthermore you get an error thrown when you attempt to set the old MobileMe resource tag.  So what’s a developer to do when you need to support multiple versions of iOS?

Sample code for configuring backup options

NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:rootPath];
NSString *fileName = nil;
NSNumber *backupValue = [NSNumber numberWithBool:YES];
while ((fileName = [enumerator nextObject])) {
    NSURL *fileURL = [NSURL fileURLWithPath:[rootPath stringByAppendingPathComponent:fileName]];
#if __IPHONE_5_1
    if (&NSURLIsExcludedFromBackupKey != nil) { // iOS >= 5.1
        [fileURL setResourceValue:backupValue forKey:NSURLIsExcludedFromBackupKey error:&error];
        if (error)
            NSLog(@"Error setting iCloud Backup value to %@ for %@: %@", backupValue, fileName, error);
    } else
        [fileURL setResourceValue:backupValue forKey:@"" error:&error];
        if (error)
            NSLog(@"Error setting MobileBackup value to %@ for %@: %@", backupValue, fileName, error);

Let me walk you through this code step-by-step.

First we fetch a directory enumerator so we can step through each file within the path that we want to configure. Each file has to be individually configured to be excluded from backups, so this is an efficient way to step through your files.

The next step, within the enumerator loop, is to fetch a file URL for the file on the local device that we want to configure.

From here the code gets a little trickier. We determine if the current device supports the iCloud backup key by checking if the NSURLIsExcludedFromBackupKey constant is defined. If this source code is compiled on Xcode 4.3, but the compiled code is running on iOS 5.0 or below, then the value for that constant will be nil, allowing us to fall back to the “else” block. However, to be on the safe side, I added a conditional compiler directive to only compile the snippet of code supporting iCloud if this source is compiled on an older version of Xcode. Over time this will become less important, but if you’re using Xcode 4.2.x or below, then the NSURLIsExcludedFromBackupKey constant won’t be defined and referencing it would be a compile-time exception.

Note: Normally you wouldn’t want to rely on using compile-time directives in your project because the compiled output of your application can run in a number of different environments, so performing run-time checks is crucial (like we’re doing here with the if/else code). The only reason we’re using this here is for circumstances where this code is compiled on an older Xcode.

Finally, in the else block, we fall back to setting the old “MobileMe” backup key.

Finishing touches

Before diving in and using this, you might want to consider how many files you’re setting backup exclusion options for, whether or not those files really should be backed up, or when you perform these updates. If you have a large number of files you may want to consider running this in an asynchronous block call, or even run it across several different blocks so you don’t block a single CPU core unnecessarily (especially on the iPad 1 or iPhone 4 where those devices only have a single-core CPU).

Additionally, if you’re setting this value to content that is able to be re-generated, or on cache or temp data, you should consider storing your local files in a different directories more suited to those tasks. Some directories (such as the Library/Caches or temporary directories) aren’t ever backed up, and are the preferred location for storing that data.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.