Wednesday, February 27, 2013

NSDocumentsDirectory and iCloud issue :preventing files in a App's Documents Directory being synced to iCloud

Many of you guys use app's  "Documents Directory" to store your files(image,video,pdf,music etc) in iOS app and being an iOS developer, it's obvious that you use documents directory since for each app,its the best way to store your files on the device permanently ,so that your app can  function properly in offline mode too.
However, if you are trying to store large number of files (obvious referring both in quantity and size) in app's documents directory, then i will say STOP & WAIT!!! 

- "STOP" because your app gonna be rejected by Apple because according to Apple's latest iOS Data Storage Guidelines : "Since iCloud backups are performed daily over Wi-Fi for each user’s iOS device, it’s important to ensure the best possible user experience by minimizing the amount of data being stored by your app." In summery i will say there are two points, every files in NSDocumnetsDirectory will be synced to user's iCloud and another is,if file sizes stored in NSDocumnetsDirectory are large then Apple will reject it since that causes the slow down on syncing process as iCloud-device syncs on daily basis.
-"WAIT" because i have a solution for it.
As pointed above if you are going to save files in NSDocumentDirectory then it will be back-up to iCloud,that means any thing you store in documents folder, will be synced to user's iCloud and will be publicly available. So if your files stored in documents directory  makes no sense by being public then you don't have to worry about anything(unless its size is bigger), but if your file  stored there are large in size and  can not be public,or i say if these files are only intended just for the scope of your app then you must have to do extra work to prevent from being synced to iCloud.

There are two solutions for it :

1. Store your files in NSCachesDirectory or NSTemporaryDirectory rather than storing it to NSDocumnetDirectory since cache/temp directory will not be synced to iCloud.
Saving to tmp directory:  
   NSString *tempDir = NSTemporaryDirectory();
NSString *savedImagePath = [tempDir stringByAppendingPathComponent:[NSString stringWithFormat:@"testImg.png"]];
Saving to Cache directory:  
NSString *cacheDir =[NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)lastObject];
NSString *savedImagePath = [cacheDir stringByAppendingPathComponent:[NSString stringWithFormat:@"testImg.png"]];
But PROBLEM , because Caches and tmp  are  the only two directories that aren’t backed up but yet are “cleaned” out when the device is low on space. That means files in these directories will be deleted anytime by iOS when there is an urgency of space in your device that leads to content unavailable(files will not always present) for your app in offline mode.

2. Store files in NSDocumnetsDirectory with added method named "Do not back up"
From iOS 5.0.1 ,we have a better solution that does not backed up to  iCloud, it’s not synced, but it’s also never deleted unless the app is deleted. Apple has added a method for developers to mark files or even folders in NSDocumnetsDirectory (outside of Caches and tmp) as “do not back up”. That leads to the conclusion that we can use NSDocumnetsDirectory for storing files and will not be synced to iCloud either.
Here's the method for it :

#include <sys/xattr.h> // Needed for function: setxattr
-(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)fileURL {
    // First ensure the file actually exists
    if (![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) {
        NSLog(@"File %@ doesn't exist!",[fileURL path]);
        return NO;
    }
    // Determine the iOS version to choose correct skipBackup method
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
    if ([currSysVer isEqualToString:@"5.0.1"]) {
        const char* filePath = [[fileURL path] fileSystemRepresentation];
        const char* attrName = "com.apple.MobileBackup";
        u_int8_t attrValue = 1;
        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
        NSLog(@"Excluded '%@' from backup",fileURL);
        return result == 0;
    }
    else if (&NSURLIsExcludedFromBackupKey) { //iOS 5.1 and later
        NSError *error = nil;
        BOOL result = [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
        if (result == NO) {
            NSLog(@"Error excluding '%@' from backup. Error: %@",fileURL, error);
            return NO;
        }
        else { // Succeeded
            NSLog(@"Excluded '%@' from backup",fileURL);
            return YES;
        }
    } else {
        // iOS version is below 5.0, no need to do anything because there will be no iCloud present
        return YES;
    }
}

// now call  above method from where you are storing your files/folders in NSDocumnetsDirectory
-(void) doNotBackUpMyFolderORFile {
  //iCloud do not back up
    NSString *applicationDocumentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// find the path of your file or folder you want to use "Do not back up"in NSDocumentDirectory 
 NSString *filePath = [applicationDocumentsDir stringByAppendingPathComponent:[NSString stringWithFormat:@"yourFileNameWithExtensionOryourFolderName"]];
// you can use "do not back up" for folders inside DocumnetsDirectory too
    NSURL *pathURL= [NSURL fileURLWithPath: filePath]; //[NSURL filePath]; //create NSURL from your path
    [self addSkipBackupAttributeToItemAtURL:pathURL]; // calling 'do not back up' method
}

That's the wrap for this section. HAPPY CODING !!!
Note: Exception for iOS 5.0  : It is not possible to exclude data from backups on iOS 5.0 only. If your app must support iOS 5.0(mainly targeted for iOS 5), then you will need to store your app data in Caches to avoid that data being backed up but indeed iOS will delete your files from the Caches directory when necessary. It's the exception for device having only  iOS 5.0 , so suit your self.
*No iCloud are present in earlier iOS version  so iCloud syncing problem will occur on device with iOS 5 and later.

2 comments:

  1. readings were really very good. I will bookmark this blog so that I could often visit this blog often to read the latest article from your blog. behind terimaksih awaited visit, .......


    By : developing mobile app indonesia | firzil.co.id

    ReplyDelete
    Replies
    1. Thank You @Ayu Setia Rini for your appreciation. I am Looking forward with the blog with new contents. Be in touch !!!

      Delete