23

EDIT: So far, the best I've been able to come up with is a pop-up to ask the user to disable iCloud sync, along with moving all the data to the Documents directory so it won't get wiped: In iOS5, is it possible to detect if a user has an app set to back up?

I develop offline mapping application for iPhone/iPad.

We used to store all of the data (many gigs potentially) in the Caches directory.

As of iOS5, the files in the Caches directory can be randomly deleted when the user's hard drive starts getting full.

How can I store local data, without the data being synced to iCloud, iTunes, and without it being randomly deleted? My local data is a large directory tree with many small data files, in thousands of subdirectories.

I moved our directory tree from the library cache directory to a data.nosync directory in the documents directory, because we had read this might be a solution. However, the data in the nosync folder is still being backed up to iCloud.

Here is now I create the directory:

NSString* noSyncDirectory() {
  static NSString *directory = nil;
  if (!directory) {
    directory = [[NSString stringWithFormat:@"%@/%@",
                  documentsDirectory(), @"data.nosync"] retain];
    [Constants createDirectoryIfNeeded:directory];
  }
  return directory;
}
Community
  • 1
  • 1
Andrew Johnson
  • 13,108
  • 13
  • 75
  • 116

6 Answers6

13

From: https://developer.apple.com/library/ios/#qa/qa1719/_index.html

You can use the following method to set the "do not back up" extended attribute. Whenever you create a file or folder that should not be backed up, write the data to the file and then call this method, passing in a URL to the file.

#include <sys/xattr.h>
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
    const char* filePath = [[URL path] fileSystemRepresentation];

    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;

    int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
}

More information can be found: https://developer.apple.com/icloud/documentation/data-storage/

Further note: While the developer documentation incorrectly implies ("These files will not be purged and will not be included in the user's iCloud or iTunes backup.") that the do-not-backup flag doubles as a do-not-purge flag that is not the case. Simply leaving files in the Caches Directory and flagging them do-not-backup will not prevent their wipe.

BadPirate
  • 25,802
  • 10
  • 92
  • 123
  • 3
    +1, but it should be pointed out that this only works on iOS 5.0.1 onward (hence all the other answers). – nschum Nov 13 '11 at 13:21
  • I'll mark this as answered, but as the other commenter points out, this was as of 5.0.1, which was changed because this as impossible before the release. – Andrew Johnson Jan 13 '12 at 21:00
  • Yeah. That's true. Perhaps a spin off question is in order, looking for work arounds that work specifically for 5.0? I've done lots of research into this particular issue and so far found no viable work around for devices on 5.0.0 – BadPirate Jan 14 '12 at 00:15
1

This issue might not have a workaround yet... You could possibly try calling URLForUbiquityContainerIdentifier explicitly, since it does some initialization on the first invocation. Then create a sub-directory with a .nosync suffix (based on this example).

The first time you call this method for a given container directory, iOS extends your application sandbox to include that container directory. Thus, it is important that you call this method at least once before trying to search for files in iCloud. And if your application accesses multiple container directories, you should call the method once for each directory.

The doc on .nosync:

To ensure that the persistent store itself is not synced by iCloud: when you set a value for the NSPersistentStoreUbiquitousContentNameKey, UIManagedDocument puts the persistent store in a .nosync directory inside the document package. If you make use of additional content (using the writeAdditionalContent:toURL:originalContentsURL:error: method), you must make sure that the document directory is not a package. Typically you give the document directory an extension that is not recognized as a document extension.

You may want to ensure you have the com.apple.developer.ubiquity-container-identifiers entitlement.

The iCloud Containers field identifies the list of container directories that your app can access in the user’s iCloud storage. (This field corresponds to the com.apple.developer.ubiquity-container-identifiers entitlement.)

malhal
  • 26,330
  • 7
  • 115
  • 133
jspcal
  • 50,847
  • 7
  • 72
  • 76
  • Could you perhaps post an example of how you're creating the ubiquity container? I haven't used them yet and the documentation is a little confusing. – smokey_the_bear Oct 14 '11 at 22:11
  • Create the .nosync inside or outside the container returned by Ubiquity? – Kendall Helmstetter Gelner Oct 14 '11 at 22:13
  • I guess I just don't know what 'the container returned by Ubiquity' means. I just get nil back from the Ubiquity related functions in NSFileManager. – smokey_the_bear Oct 14 '11 at 22:17
  • I'd try inside ubiquity (based on [this doc](http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/_index.html) which seems to suggest `.nosync` will be obeyed in a ubiquity directory). Not sure why it gives `nil`, possibly an entitlement issue (?) – jspcal Oct 14 '11 at 22:44
  • 1
    The thing is, after reading through those documents I think they only relate to prevention of syncing of data through iCloud to other app instances - not necessarily to backing up, the directories may well still be backed up. – Kendall Helmstetter Gelner Oct 14 '11 at 22:51
1

Perhaps you can disable backup for your app and store data files somewhere else in the app tree. Any stuff that needs to be backed can be put in a common area outside your app.

You might be able to do it in provisioning: invalid code signing app submission

or settings:

When you want to give the user the option to enable or disable iCloud usage entirely for your app. If your app includes a Settings bundle or inline preferences, you could include a preference to toggle whether your app stores content in iCloud at all. For example, an app whose data consists entirely of privately managed files might do this to give the user the choice of how those files are stored.

or by removing the com.apple.developer.ubiquity-container-identifiers entitlement (which could get auto-added) with Xcode: Configuring Your App's iCloud Entitlements

Otherwise you might need to issue a warning with instructions on disabling through the UI:

http://www.pcmag.com/article2/0,2817,2394702,00.asp#fbid=bpIwPLZ1HeQ

Another workaround is to group the maps into collections that are installed as separate applications. That would be a way to store the data without creating any directories that sync or get backed-up. The data will be stored in the the .app directory and will be protected.

Depending on how the cache space reclamation function works, it might not delete recently accessed or modified files. You could try periodically touching them on a timer. You could also add some old files as decoys and detect when they've been deleted or when space is low to at least issue a warning or re-download the deleted objects...

Community
  • 1
  • 1
jspcal
  • 50,847
  • 7
  • 72
  • 76
  • I think we all need to see more testing on how cache clearing behaves, my theory is also that it would remove recently created items last so touching them on app launch/awaken would make it more likely the offline data would be left alone if at all possible. – Kendall Helmstetter Gelner Oct 15 '11 at 09:00
0

There is a newer way to prevent iCloud syncing of data without using extended attributes directly:

- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
{
    NSURL* URL= [NSURL fileURLWithPath: filePathString];
    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);

    NSError *error = nil;
    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
                                  forKey: NSURLIsExcludedFromBackupKey error: &error];
    if(!success){
        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
    }
    return success;
}

See Apple's Technical Q&A QA1719 for more details.

Dalmazio
  • 1,835
  • 2
  • 23
  • 40
0

Maybe uncheck "Enable Entitlements" in the summary pane of the project or edit the profile to remove the *ubiquity settings. (Notes for ios 5 beta 7 reference entitlements.)

There is also the setUbiquitous function:

setUbiquitous:itemAtURL:destinationURL:error:

Sets whether the item at the specified URL should be stored in the cloud.

Parameters

flag

Specify YES to move the item to iCloud or NO to remove it from iCloud (if it is there currently).

http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html#//apple_ref/occ/instm/NSFileManager/setUbiquitous:itemAtURL:destinationURL:error:

Community
  • 1
  • 1
jspcal
  • 50,847
  • 7
  • 72
  • 76
-6

Did you try just naming the directory ".nosync" without the data in front? It could be generally . directories are ignored, or perhaps that specifically.

But, it seems like the behavior is just as a user would want it - potentially gigs of space used by an application they may not be using currently, where the space could be reclaimed automatically. I am not sure but you would think the system would be smart about reclaiming files created recently only after there was no choice, so if the user had just stored maps recently they would not be deleted unless there was no other choice.

For the purposes of filing a bug I am going to ask for a way to mark a directory for user prompting before deletion - so that if they are syncing a lot of movies and it would clear out a data set like the maps you are talking about, the user would be asked if they want to remove the data "offline maps" from application "MyCoolMapper" to proceed with the sync.

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • "But, it seems like the behavior is just as a user would want it " - No, users explicitly download sets of map tiles, that they will need when they are in the forest, or flying their airplane, or sailing their boat. They do not want them deleted until they press the Delete button for that saved Map. This new "feature" likely breaks nearly every popular map application in existence, not to mention Instapaper. – Andrew Johnson Oct 14 '11 at 22:12
  • Hidden files seem to be synced just the same, .nosync or .otherwise. – smokey_the_bear Oct 14 '11 at 22:12
  • I use a number of offline map applications. But some of them I don't use often, I don't care if the offline maps go away or not, and in fact would prefer they would be automatically cleaned out by the system. But I wouldn't mind as I said the system prompting me for some application data to remind me it was going to be removed in a sync, I think that is preferable to creating a "permanent cache" directory that most application developers will switch to using over the real one if given choice. What I would do with an offline mapping application is update the tile timestamps on launch. – Kendall Helmstetter Gelner Oct 14 '11 at 22:54
  • 1
    I appreciate you trying to help Kendall, but I think you just don't get it at all. Imagine I have a Flight Charts application being used by the Air Force. The app automatically downloads charts for the entire US, and when the charts are soon to be updated, it also grabs them ahead of time so it can update the charts when the cycle changes, without needing to spend hours downloading new charts. Now imagine when they go to fly and they turn the app on, half of the charts disappear. Are you suggesting I architect the app to go ahead and download the new maps when this happens? – Andrew Johnson Oct 14 '11 at 23:05
  • Because if that's your suggestion, the Air Force no longer wants to use my app. Those charts darn well better be on board when they expect them to be. They need to be able to reliably have both gigs of current charts, and gigs of upcoming charts, ready to fly. They might fly to China or elsewhere they won't be able to update, enroute. – Andrew Johnson Oct 14 '11 at 23:06
  • Then in that case why would you NOT want the charts backed up? Caches exists for a reason. Permanent store is there for a reason. Also as I said, an intelligent caching system would not remove those charts because (a) there is no need ( Presumably the air force guy is not allowed to sync unapproved content to the device thus not triggering a clean ) and (b) it would remove other things before those charts because they had been recently used. If it's really vital that data stays put then it is JUST as vital it get backed up too. But as a user of offline maps that data is usually not vital. – Kendall Helmstetter Gelner Oct 14 '11 at 23:47
  • 1
    @Kendall - you're just wrong on this. Read http://www.marco.org/2011/10/13/ios5-caches-cleaning. If you put the data in Caches, it can get deleted with no warning. If you put it in /Documents, your app can get rejected. Apple screwed this up big time. – Flyingdiver Oct 15 '11 at 00:45
  • @Flying - I have already read that page. Note that the note from Apple DOES NOT say that your application would be rejected, it's just letting you know the implications of where you have put data. While the experience right now is not great in some cases, my solution (bug filing in post) is the compromise between a users device filling up with things they do not remember they have, and the way they have things now. They may have gone a bit too far the other way but I think on the whole it's better for users even if developers have to struggle with it. As a user - load offline data last. – Kendall Helmstetter Gelner Oct 15 '11 at 08:56
  • The actual text from Apple's document reads: "Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application" should be put in /Documents. Map data meant for offline use CANNOT be recreated or downloaded, therefore should be put into /Documents. Yes it gives mapping applications as an example of things that should store data in caches, but that's more for something like Waze where it's just storing tiles temporarily. – Kendall Helmstetter Gelner Oct 15 '11 at 09:11