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;
}
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.
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...
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.)
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:
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.
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.
Related
I am able getting data from server and display on UITableView, It's working fine. But when I go to another tab and return to same tab it will call to server for data. I want to store data locally once get from server.
Please suggest me your thoughts.
Thanks!
You have to store the file on the device and this is possible in a few ways:
Store the files in the App its Documents directory
Store the files in the App its Library/Caches folder
Store the files in the App its Library/ folder
There are a few considerations for each option:
Files stored in the Documents folder are by default backed up by iCloud. You really have to think if this is necessary because you're eating up iCloud storage space from the user (for which he/she may pay).
Storing files in the Caches folder is a good way, but they may be deleted by iOS if the users disk is running low on space (therefore the name Cache).
Storing files in another (manually created) folder on the device will keep them as long as the app is installed. No iCloud backup or no removal by iOS
Getting the location of the specific folder can be done using:
[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
Where you have to replace NSDocumentDirectory by NSLibraryDirectory when you want the NSURL to the Library folder.
Storing data is as easy as creating an NSURL which points to the location where you want to store the data and then call the writeToURL:atomically: method on the NSData you want to store.
Converting UIImages to NSData can be done using UIImageJPEGRepresentation or UIImagePNGRepresentation.
i would prefer to use the HJCache library for the temporally store images.
For that followings are the reasons to implement the HJCache open source library.
Make it easy to use asynchronously loaded images in iOS apps.
Make it easy to use a shared file cache.
Make memory management ‘just work’ according to Cocoa norms.
Support typical cases of viewing images, eg scrolling through a list of tweets showing the tweeters profile pic, swiping through a photo album loaded over the net.
Allow the library to be used for other kinds of data too.
For more detail and tutorial visit the github and author blog
We're storing data into Document directory of application and we got rejection of application. We've tried with "do not back up" attribute for storing data in current version with below code.
- (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;
}
We've used it as per iOS Data Storage Guidelines.
In earlier version we've also tried with storing data in Private directory. But, we were not able to get approval of application.
Can you please give us some more description regarding why we are not able to get approval of application? or yet, we need any other changes in code regarding data storage? So, that we can get approval and we've new version of application on iTunes.
I think #Jasarien's comment is the right answer, but there was no further comm, and this question is relatively new, so I'll expand.
For others to see the rejection you get:
The iOS Data Storage Guidelines specify:
Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application, should be stored in the /Documents directory and will be automatically backed up by iCloud.
Data that can be downloaded again or regenerated should be stored in the /Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications.
Data that is used only temporarily should be stored in the /tmp directory. Although these files are not backed up to iCloud, remember to delete those files when you are done with them so that they do not continue to consume space on the user’s device.
Use the "do not back up" attribute for specifying files that should remain on device, even in low storage situations. Use this attribute with data that can be recreated but needs to persist even in low storage situations for proper functioning of your app or because customers expect it to be available during offline use. This attribute works on marked files regardless of what directory they are in, including the Documents directory. These files will not be purged and will not be included in the user's iCloud or iTunes backup. Because these files do use on-device storage space, your app is responsible for monitoring and purging these files periodically."
For example, only content that the user creates using your app, e.g., documents, new files, edits, etc., may be stored in the /Documents directory - and backed up by iCloud.
Temporary files used by your app should only be stored in the /tmp directory; please remember to delete the files stored in this location when the user exits the app.
Data that can be recreated but must persist for proper functioning of your app - or because customers expect it to be available for offline use - should be marked with the "do not back up" attribute. For more information, please see Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes?.
So I see why you would use "do not back up" and expect to be in compliance, but I think like #Jasarien said - They mean for you to move to more discrete directories like cache or temp.
In fact what would definitely pass the review is switching to Core Data and using the internal SQLite - but that's probably too much work.
So to wrap up - a post on how to save to caches or tmp -
Where to save files in iOS 5 applications?
(actually, maybe this was all a duplicate of that one... :-/)
GL!
Oded
-- Edit --
Another good post:
https://stackoverflow.com/questions/8164868/ios-data-storage-guidelines-available-since-when
-- Edit #2 --
And another good post:
iOS 5 does not allow to store downloaded data in Documents directory?
Guess I should've just pointed out the duplicates... :)
Have a look at this link http://developer.apple.com/library/ios/#qa/qa1719/_index.html#//apple_ref/doc/uid/DTS40011342
The code you are using is compatible for 5.0.1 and not 5.1 and later. You have to use Cache for <=5.0 . But the better solution would be to deploy for 5.1 and later instead of 5.0 because if you have large amounts of data, it is likely that the Cache will be cleared sooner of later. Hope it helps.
I have made an application for my client by keeping target iOS as 4.
But since the application still not submitted to Apple store, my client is planning to upgrade it for iOS 5.0.
For this I read the guideline from Apple and found that "Only user-generated data or that cannot otherwise be recreated by your application, should be stored in the /Documents directory and rest should be stored to /Library/Caches directory"
In my application, I am using server model of in-app purchase for non-consumable product. For this I am storing all my downloaded data (which are basically books or magazines) to Documents directory. The Database is also present in the same directory which contains the details about the downloaded products.
My question is,
1. Should I have to change my code to store the downloaded data to Library/Caches directory instead of to the Documents directory?
2. Where should my database file be placed (to Documents or Caches)?
If I put it products in the Caches then I have to change the logic of retrieval also, since it is considered that if record is present in database, there is no need change the existence of the file and it directly opens it when user clicks on the magazine.
Kindly guide me on this issue.
Thanks in advance.
UPDATED:
I am updating this for those who are still not sure about this problem.
Using the guideline of accepted answer, I have implemented this in 2 of my applications and submitted them to Apple Store. Both were approved in review.
This may promote that the solution suggested in the accepted answer is correct.
Here are the trade-offs:
If you put your files in the Documents directory then they are backed up to iTunes or iCloud but if they are too big and it's possible to download the files again then Apple may reject your app
If you put your files in the Cache directory then they won't be backed up and Apple won't reject your app. However, when iOS 5 gets low on space it may delete all the files in there.
However, with iOS 5.0.1 there is a third option:
Put files in Documents but flag them so that they are not backed up. There's a technote (QA1719) on how to do this.
I think this is probably the best answer for you.
1. Should I have to change my code to store the downloaded data to Library/Caches directory instead of to the Documents directory? = Yes, you need to store the downloaded data to Library/Caches directory.
2. Where should my database file be placed (to Documents or Caches)? = You can keep the database in Documents directory.
One app I know was once rejected because of this. Storing downloadable data in Documents dir is not recommended. The logic behind it is that your data should not unnecessarily inflate the app directory. This app directory is backed up in iCloud, so inflated app directory will cause more data to be saved in iCloud.
If data can be downloaded again, like magazines, books pdf etc. Then keep it in Caches directory. Of course you can store pointers to data (like URLs etc) in the Documents directory so that the user can retrieve them later.
To answer your questions:
Yes, change your code to load DB from Documents and Data from Caches.
Keep database in Documents
You'll have to add code to check if a document in DB exists in Caches, if it doesn't your app should download it again.
On top of the suggestion that you should keep the data in cache directory, there is a concern that you should keep in mind when keeping the data into cache folder:
Whenever iOS feels the memory crunch it deletes all the cache and temp folder.
Problem is described here in detail
To protect these directory not to delete and keep everything for lifetime you should use the attribute that will keep the mentioned directory safe.
Solution is here:
I was looking for same query and I have got the solution .
According to the apple data storage documentation dev may stored the data in cache even user want to persist that data in low memory situation.develper just need to set Do not back up flag.
Use the "do not back up" attribute for specifying files that should remain on device,
even in low storage situations. Use this attribute with data that can be recreated but
needs to persist even in low storage situations for proper functioning of your app or
because customers expect it to be available during offline use.
This attribute works on marked files regardless of what directory they are in, including the Documents directory.
These files will not be purged and will not be included in the user's iCloud
or iTunes backup. Because these files do use on-device storage space, your app is
responsible for monitoring and purging these files periodically.
For more Information go through this link
Use Below Piece of code for setting the do not back up Flag.
import <sys/xattr.h>
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
if (&NSURLIsExcludedFromBackupKey == nil) { // iOS <= 5.0.1
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;
} else { // iOS >= 5.1
NSError *error = nil;
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
return error == nil;
}
}
URL is the path of Data.
I have made an application for my client by keeping target iOS as 4.
But since the application still not submitted to Apple store, my
client is planning to upgrade it for iOS 5.0
Another option (if you are only going to support iOS 5) is to use NewsstandKit. This way only selected issues of the magazine will be deleted by iOS when low on "disk" space. My understanding is that iOS will delete selected issues based on the last time read and maybe the size.
If you don't use newsstand and your app is targeted, all the issues will be deleted.
I, like many developers, got an email from Apple recently that stated we should move our data from the documents directory into another folder to permit more streamlined backup to iCloud.
In recent testing it appears that [your app] stores a fair amount of data in its Documents folder.
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.
Marco Arment, of instapaper fame, has a good take on the issue, which is that the recommended location for storing downloadable files is in /Library/Caches. However, the problem is that both /tmp and /Caches can be 'cleaned' anytime the OS decides that the device is running low on storage. If your app is cleaned then the data downloaded by your app and stored by your user is gone. Naturally, the user will blame you and not Apple.
What to do?
iOS 5.0.1 introduced a flag to address this issue:
https://developer.apple.com/library/ios/#qa/qa1719/_index.html
Their recommendation is to create a folder in /Library/ like /Library/PrivateDocs , and put your files there. However you will also have to set a "do not backup" flag on them as every file in /Library except for those in /Library/Cache or tmp are backed up by default. Set the flag on PrivateDocs folder with this command:
#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;
}
Library/Caches is probably a good answer for many apps. Especially when the app will work fine is cached data is lost and clearing the cache does not also destroy all record of what data a user might have elected to cache and where it can be re-obtained from.
For apps which have data which does not belong in Caches consider Library/Application Support.
http://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1
Application Support:
Use this directory to store all application data files except those associated with the user’s documents. For example, you might use this directory to store application-created data files, configuration files, templates, or other fixed or modifiable resources that are managed by the application. An application might use this directory to store a modifiable copy of resources contained initially in the application’s bundle. A game might use this directory to store new levels purchased by the user and downloaded from a server.
All content in this directory should be placed in a custom subdirectory whose name is that of your application’s bundle identifier or your company.
In iOS, the contents of this directory are backed up by iTunes.
Unfortunately the Application Support directory is still backed up and falls under Apple's new data storage guidelines. Depending on how sensitive reviewers choose to be about total backed up file size this may still lead to rejections.
I'm struggling a bit with the idea of iCloud and posted a more general question here. My biggest problem is to decide whether I should stop putting the user's data in the good old documents folder which is found in the app's sandbox. To illustrate my problem:
The docs don't give an answer as far as I can see. Let's suppose I have an App which handles different txt files. Once I start my app, I simply check if any txt files are in the cloud like so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(#"AppDelegate: app did finish launching");
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
// (1) iCloud: init
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"User has iCloud enabled! Let's get the txt files from the cloud.");
[self loadDocument];
} else {
NSLog(#"User doesn't have iCloud enabled. App is thus worthless.");
}
return YES;
}
I then have a method to check if there are any txt files in the cloud and if so, load them. If not, I simply create new txt files in the cloud.
This means that the app does not store any data in the documents folder. As far as I understand it, everything is either in the local iCloud storage of my device (which is also accessible if the user is OFFLINE) or in the cloud. So the text file exists in two places: on my device and the cloud.
So there is simply no need to store a third copy in my local documents folder, right? Or is this essential for some reason I have overlooked? In other words, for what should I use the local documents folder if I offer iCloud to my users? (And can I simply ignore those who won't sign up for iCloud?)
EDIT: Just to clarify, when I'm talking about the standard documents folder in the app's sandbox, I mean this one:
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
Perhaps I'm a bit slow, by re-reading the docs for the 4th or 5th time, I came across this which suggests that you should always create your files in the sandbox and then move them to the cloud. So in a way, Apple suggests to have 3 versions of the same file at all times:
Apps use the same technologies to manage files and directories in
iCloud that they do for local files and directories. Files and
directories in iCloud are still just files and directories. You can
open them, create them, move them, copy them, read and write from
them, delete them, or any of the other operations you might want to
do. The only differences between local files and directories and
iCloud files and directories is the URL you use to access them.
Instead of URLs being relative to your app’s sandbox, URLs for iCloud
files and directories are relative to the corresponding iCloud
container directory.
To move a file or directory to iCloud:
Create the file or directory locally in your app sandbox. While in
use, the file or directory must be managed by a file presenter, such
as a UIDocument object.
Use the URLForUbiquityContainerIdentifier: method to retrieve a URL
for the iCloud container directory in which you want to store the
item. Use the container directory URL to build a new URL that
specifies the item’s location in iCloud. Call the
setUbiquitous:itemAtURL:destinationURL:error: method of NSFileManager
to move the item to iCloud. Never call this method from your app’s
main thread; doing so could block your main thread for an extended
period of time or cause a deadlock with one of your app’s own file
presenters. When you move a file or directory to iCloud, the system
copies that item out of your app sandbox and into a private local
directory so that it can be monitored by the iCloud daemon. Even
though the file is no longer in your sandbox, your app still has full
access to it. Although a copy of the file remains local to the current
device, the file is also sent to iCloud so that it can be distributed
to other devices. The iCloud daemon handles all of the work of making
sure that the local copies are the same. So from the perspective of
your app, the file just is in iCloud.
All changes you make to a file or directory in iCloud must be made
using a file coordinator object. These changes include moving,
deleting, copying, or renaming the item. The file coordinator ensures
that the iCloud daemon does not change the file or directory at the
same time and ensures that other interested parties are notified of
the changes you make.
See here.
There is no reason to store documents both in local storage as well as in iCloud. However, you should give users the option of turning iCloud storage off. With iCloud storage off, you should only look for files in local storage (as with pre-iOS5 apps). The best thing is to try to isolate the part of your code that needs to know where documents are stored, and have it test whether or not iCloud is available and enabled and have that piece of code return the URL where documents should be stored.
Update:
If you want to diverge for iOS5 vs. iOS4, you just need to test if the iOS5 features are there. There are several ways that you can do this. One way is to check:
if ([UIDocument class] == nil)
On iOS4 this will be true, and on iOS5 this will be false. I don't know what kind of datastructures you have for your files, but one thing you could do is create a wrapper around UIDocument. Inside this wrapper class, you could have instance variables for a UIDocument structure as well as for fields you would need in IOS4 (such as the path to the file). When you instantiate your class, test whether iCloud is enabled and if UIDocument is available, and if so, use it and set the field. Otherwise, set the other fields and leave the UIDocument field to be nil. When you need to do operations on your "file", test if the UIDocument field is nil, and if it is do it the "old" way. Otherwise, just pass on the request to the UIDocument object.
Remember that if the user has enabled device backup to iCloud, the documents directory gets backed up anyways.
It really depends on if your planning to use iCloud as a sync tool between apps / platforms. If your not, it really doesn't make sense to use iCloud to store your documents.