App rejected due to data storeage - ios

My app was rejected cause it must follow the iOS Data Storage Guidelines. I have already read some answer here on stackoverflow, and i have already read some blogs... I know my problem, at first application launch i download unzip sqlite db file from server and zip it ,after that i remove unzip file from temp folder.
I am using following code also.
+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL {
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;
}
calling this method here:-
+ (void) createEditableCopyOfDatabaseIfNeeded
{
NSLog(#"Creating editable copy of database");
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:ddb];
[self addSkipBackupAttributeToItemAtURL:[NSURL URLWithString:writableDBPath]];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
{
NSLog(#"ALready exists"); return;
}
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:ddb];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
[self addSkipBackupAttributeToItemAtURL:[NSURL URLWithString:defaultDBPath]];
if (!success)
{
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
NSURL * fileURL;
fileURL = [ NSURL fileURLWithPath: ddb ];
[ fileURL setResourceValue: [ NSNumber numberWithBool: YES ] forKey: NSURLIsExcludedFromBackupKey error: nil ];
}
Still my application was rejected.Please help.
I got this respones from apple.
May 12, 2016 at 1:59 AM
From Apple
2.23 - Apps must follow the iOS Data Storage Guidelines or they will be rejected
Thank you for your resubmission. During our continued review, we found the following issue unresolved:
2.23 Details
On launch and content download, your app still stores 78.84 MB on the user's iCloud, which does not comply with the iOS Data Storage Guidelines.
Next Steps
Please verify that only the content that the user creates using your app, e.g., documents, new files, edits, etc. is backed up by iCloud as required by the iOS Data Storage Guidelines. Also, check that any temporary files used by your app are only stored in the /tmp directory; please remember to remove or delete the files stored in this location when it is determined they are no longer needed.
Data that can be recreated but must persist for proper functioning of your app - or because users expect it to be available for offline use - should be marked with the "do not back up" attribute. For NSURL objects, add the NSURLIsExcludedFromBackupKey attribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCRUFLIsExcludedFromBackupKey attribute.
Resources
To check how much data your app is storing:
- Install and launch your app
- Go to Settings > iCloud > Storage > Manage Storage
- Select your device
- If necessary, tap "Show all apps"
- Check your app's storage
For additional information on preventing files from being backed up to iCloud and iTunes, see Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes.
If you have difficulty reproducing a reported issue, please try testing the workflow described in Technical Q&A QA1764: How to reproduce bugs reported against App Store submissions.
If you have code-level questions after utilizing the above resources, you may wish to consult with Apple Developer Technical Support. When the DTS engineer follows up with you, please be ready to provide:
- complete details of your rejection issue(s)
- screenshots
- steps to reproduce the issue(s)
- symbolicated crash logs - if your issue results in a crash log

NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:ddb];
This kinda points out ddb is some random string.
NSURL * fileURL;
fileURL = [ NSURL fileURLWithPath: ddb ];
[fileURL setResourceValue: [ NSNumber numberWithBool: YES ] forKey: NSURLIsExcludedFromBackupKey error: nil ];
here you are not getting the path of where you copied the db, instead
just opening url with string value which is why i think it is failing.
To fix this call the method after this line
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
[self addSkipBackupAttributeToItemAtURL:[NSURL URLWithString:writableDBPath]];

(1) Make sure your large file is located in the Library folder.
(2) Use the NSURLIsExcludedFromBackupKey option or the addSkipBackupAttributeToItemAtPath in AppDelegate.m .
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* create a file outside of the Document folder like filePath1 */
/* preven os from copying data file for iCloud */
[self addSkipBackupAttributeToItemAtPath:[self filePathA]];
return YES;
}
- (NSString *)filePathA {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);
return [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Data"];
}
- (NSString *)filePath1 {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask,YES);
return [[[paths objectAtIndex:0] stringByAppendingPathComponent:#"Data"] stringByAppendingPathComponent:#"Data1.data"];
}
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *)path {
NSURL *url = [NSURL fileURLWithPath: path];
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;
}

Related

App Crashes at startup on App Store review - works perfectly on devices

I have an app that crashes on startup in the app store review, but works perfectly otherwise on devices and on the simulators. Here are the specifics.
The app is written for IOS 7, and is being tested on IOS 8.x. I symbolicated the crash logs from Apple, and it is crashing on the first attempt to access information stored in the pre-populated Core Data sqlite db.
This code does the db copy at startup:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CC.sqlite"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *storePath = [documentsDirectory stringByAppendingPathComponent: #"CC.sqlite"];
// Check if the sqlite store exists
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) {
NSLog(#"sqlite db not found... copy into place");
// copy the sqlite files to the store location.
NSString *bundleStore = [[NSBundle mainBundle] pathForResource:#"CC" ofType:#"sqlite"];
[[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:nil];
}
else {
NSLog(#"Already exists");
}
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{ NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"} };
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"error %#, %#", error, [error userInfo]);
}
return __persistentStoreCoordinator;
}
We have run this a couple of hundred times on various IOS devices (by several different people) without any problems at all, but invariably get a startup crash at Apple.
It is clear to me from the crash logs that the app (during app store review) is trying to access a non-existant sqlite db through Core Data, but I have no idea why this is happening only at Apple, and why I cannot reproduce the error. I am not sure what other info to add to the question but will happily update as required.
Any advice gratefully received....
I will answer my own question here, although the code changes I made while trying to solve the problem makes posting the code not very helpful.
I had to put the startup database operations in a completion block. This involved moving everything into my initial startup view controller, and disabling my tab bar buttons while the database was initialized.
What was happening was certain items in the Core Data sqlite database were being required before it was completely preloaded, thus the crash.
More on blocks can be found here

Save iOS 8 Documents to iCloud Drive

I want to have my app save the documents it creates to iCloud Drive, but I am having a hard time following along with what Apple has written. Here is what I have so far, but I'm not for sure where to go from here.
UPDATE2
I have the following in my code to manually save a document to iCloud Drive:
- (void)initializeiCloudAccessWithCompletion:(void (^)(BOOL available)) completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.ubiquityURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (self.ubiquityURL != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"iCloud available at: %#", self.ubiquityURL);
completion(TRUE);
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"iCloud not available");
completion(FALSE);
});
}
});
}
if (buttonIndex == 4) {
[self initializeiCloudAccessWithCompletion:^(BOOL available) {
_iCloudAvailable = available;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:selectedCountry];
NSURL* url = [NSURL fileURLWithPath: pdfPath];
[self.manager setUbiquitous:YES itemAtURL:url destinationURL:self.ubiquityURL error:nil];
}];
}
I have the entitlements set up for the App ID and in Xcode itself. I click the button to save to iCloud Drive, and no errors pop up, the app doesn't crash, but nothing shows up on my Mac in iCloud Drive. The app is running on my iPhone 6 Plus via Test Flight while using iOS 8.1.1.
If I run it on Simulator (I know that it won't work due to iCloud Drive not working with simulator), I get the crash error: 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[3]'
Well, you've got me interested in this matter myself and as a result I've spent way to much time on this question, but now that I've got it working I hope it helps you as well!
To see what actually happens in the background, you can have a look at ~/Library/Mobile Documents/, as this is the folder where the files eventually will show up. Another very cool utility is brctl, to monitor what happens on your mac after storing a file in the iCloud. Run brctl log --wait --shorten from a Terminal window to start the log.
First thing to do, after enabling the iCloud ability (with iCloud documents selected), is provide information for iCloud Drive Support (Enabling iCloud Drive Support). I also had to bump my bundle version before running the app again; took me some time to figure this out. Add the following to your info.plist:
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.YOUR_BUNDLE_IDENTIFIER</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
<key>NSUbiquitousContainerName</key>
<string>iCloudDriveDemo</string>
</dict>
</dict>
Next up, the code:
- (IBAction)btnStoreTapped:(id)sender {
// Let's get the root directory for storing the file on iCloud Drive
[self rootDirectoryForICloud:^(NSURL *ubiquityURL) {
NSLog(#"1. ubiquityURL = %#", ubiquityURL);
if (ubiquityURL) {
// We also need the 'local' URL to the file we want to store
NSURL *localURL = [self localPathForResource:#"demo" ofType:#"pdf"];
NSLog(#"2. localURL = %#", localURL);
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
NSLog(#"3. ubiquityURL = %#", ubiquityURL);
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}
}
else {
NSLog(#"Could not retrieve a ubiquityURL");
}
}];
}
- (void)rootDirectoryForICloud:(void (^)(NSURL *))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
if (rootDirectory) {
if (![[NSFileManager defaultManager] fileExistsAtPath:rootDirectory.path isDirectory:nil]) {
NSLog(#"Create directory");
[[NSFileManager defaultManager] createDirectoryAtURL:rootDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(rootDirectory);
});
});
}
- (NSURL *)localPathForResource:(NSString *)resource ofType:(NSString *)type {
NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *resourcePath = [[documentsDirectory stringByAppendingPathComponent:resource] stringByAppendingPathExtension:type];
return [NSURL fileURLWithPath:resourcePath];
}
I have a file called demo.pdf stored in the Documents folder, which I'll be 'uploading'.
I'll highlight some parts:
URLForUbiquityContainerIdentifier: provides the root directory for storing files, if you want to them to show up in de iCloud Drive on your Mac, then you need to store them in the Documents folder, so here we add that folder to the root:
NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:#"Documents"];
You also need to add the file name to the URL, here I copy the filename from the localURL (which is demo.pdf):
// Now, append the local filename to the ubiquityURL
ubiquityURL = [ubiquityURL URLByAppendingPathComponent:localURL.lastPathComponent];
And that's basically it...
As a bonus, check out how you can provide an NSError pointer to get potential error information:
// And finish up the 'store' action
NSError *error;
if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) {
NSLog(#"Error occurred: %#", error);
}
If you are intending to work with UIDocument and iCloud, this guide from Apple is pretty good:
https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/Introduction/Introduction.html
EDITED:
Don't know of any better guide of hand, so this may help:
You will need to fetch the ubiquityURL using the URLForUbuiquityContainerIdentifier function on NSFileManager (which should be done asynchronously).
Once that is done, you can use code like the following to create your document.
NSString* fileName = #"sampledoc";
NSURL* fileURL = [[self.ubiquityURL URLByAppendingPathComponent:#"Documents" isDirectory:YES] URLByAppendingPathComponent:fileName isDirectory:NO];
UIManagedDocument* document = [[UIManagedDocument alloc] initWithFileURL:fileURL];
document.persistentStoreOptions = #{
NSMigratePersistentStoresAutomaticallyOption : #(YES),
NSInferMappingModelAutomaticallyOption: #(YES),
NSPersistentStoreUbiquitousContentNameKey: fileName,
NSPersistentStoreUbiquitousContentURLKey: [self.ubiquityURL URLByAppendingPathComponent:#"TransactionLogs" isDirectory:YES]
};
[document saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
}];
You'll also want to look into using NSMetadataQuery to detect documents uploaded from other devices and potentially queue them for download, and observing the NSPersistentStoreDidImportUbiquitousContentChangesNotification to find about changes made via iCloud, among other things.
** Edit 2 **
Looks like you are trying to save a PDF file, which is not quite what Apple considers a "document" in terms of iCloud syncing. No need to use UIManagedDocument. Remove the last 3 lines of your completion handler and instead just use NSFileManager's
setUbiquitous:itemAtURL:destinationURL:error: function. The first URL should be a local path to the PDF. The second URL should be the path within the ubiquiuty container to save as.
You may also need to look into NSFileCoordinator perhaps.
I think this guide from Apple may be the most relevant:
https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/iCloud/iCloud.html

iOS Data Storage Rejection

My app got rejected because Apple found that on launch and/or content download, my app stores 14.18 MB.
Now I'm trying to skip backup of all the images and sounds I use in the game.
So far, I made a folder called "Resources" in my app folder itself, looking like this: App Folder Scrshot
What I did in AppDelegate.m is next:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self addSkipBackupAttributeToItemAtURL:[self applicationDocumentsDirectory]];
return YES;
}
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
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;
}
This isn't working.
I have 2 questions:
1)Am I doing the right thing from the start? Should I put all the images and sounds in folder called Resources, and then skipBackup for the entire folder or should I put them somewhere else?
If yes, then where?
(I saw over internet people talking about "Documents" folder...but I don't know what that folder is nor where to find it.
2)If I could put everything in "Resources" folder, how to I reach that folder from the code? How do I make URL to that folder, from Xcode itself?
Thanks in advance
You should deliver the content with your app. downloading the content on first launch is not a good idea due to the connection issue if user is on 3G/Edge.
the document directory is on the device, a folder under your app, and its created by ios automatically. Apple Documentation
getting files from resource folder is easy files from resources
I had this problem before ! The problem is not the size of what you store, it is where you store it. My guess is that you store your file in NSDocumentDirectory, which is bad because this directory should only be used to store data that the user created himself (documents, photo, etc). Adding the NSURLIsExcludedFromBackupKey is not enough. When it happened to me, I changed NSDocumentDirectory to NSApplicationSupportDirectory and my app got approved.
UPDATE
It is best if you first create a subdirectory in your NSApplicationSupportDirectory to store your file there.
Your methods should look like something like :
- (NSURL *) applicationDocumentsDirectory{
NSString *appSupportDir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
appSupportDir = [appSupportDir stringByAppendingPathComponent:#"MyAppDirectory"];
return [NSURL URLWithString:appSupportDir];
}
And don't forget to create the directory first :
if (![[NSFileManager defaultManager] fileExistsAtPath:[self applicationDocumentsDirectory] isDirectory:NULL]) {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:[self applicationDocumentsDirectory] withIntermediateDirectories:YES attributes:nil error:&error];
}

newsstand memory storage issue, how do i get the app cache directory?

I have a newsstand app which has magazines and uses the newsstand framework. I realized there was something wrong when deleting the magazines and/or when downloading them because when I accessed settings/usage my app keeps growing in memory usage when downloading and deleting the same magazine.
Found the issue... when downloading the issue in the delegate method:
-(void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
I just needed to add something like this at the end:
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[destinationURL path] error:&error];
if (error){
NSLog(#"ERROR:%#", error);
}
Even the directory is called "caches" you need to manually delete. Ok problem solved but what about the customers who already download my app and have tons of MBs dead in the cache directory.
I wanted to know how to get this directory and delete everything on it at launch and only once...
I can do it only once using a NSUserdefault but how do I get this directory and delete any zip files in it... an example of this directory and a file within is:
/private/var/mobile/Applications/1291CC20-C55F-48F6-86B6-B0909F887C58/Library/Caches/bgdl-280-6e4e063c922d1f58.zip
but this path varies with the device. I want to do this at launch so I'm sure there are no downloads in progress but any other solutions are welcome, thanks in advance.
Everything that you need is enumerate all files from Caches directory and remove ones that have zip extension:
- (void)removeZipFilesFromCachesDirectory {
static NSString *const kZIPExtension = #"zip";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *cachesDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSError *error = nil;
NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:cachesDirectoryPath error:&error];
if (error == nil) {
for (NSString *fileName in fileNames) {
NSString *filePath = [cachesDirectoryPath stringByAppendingPathComponent:fileName];
if ([filePath.pathExtension.lowercaseString isEqualToString:kZIPExtension]) {
NSError *anError = nil;
[fileManager removeItemAtPath:filePath error:&anError];
if (anError != nil) {
NSLog(#"%#", anError);
}
}
}
} else {
NSLog(#"%#", error);
}
}

iCloud and Core Data pre-filled database

I have an app with a pre-filled .sqlite file that is copied into the user's Documents directory when the app is first opened. This file is 12.9MB. Twice now, my app has been rejected since changing target to iOS5 with this rejection note:
Binary Rejected Apr 24, 2012 10:12 AM
Reasons for Rejection:
2.23 Apps must follow the iOS Data Storage Guidelines or they will be rejected
Apr 24, 2012 10:12 AM. From Apple.
2.23
We found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.
In particular, we found that on content download, your app stores 12.81 MB. To check how much data your app is storing:
Install and launch your app
Go to Settings > iCloud > Storage & Backup > Manage Storage
If necessary, tap "Show all apps"
Check your app's storage
The iOS Data Storage Guidelines indicate that 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 NSURL objects, add the NSURLIsExcludedFromBackupKey attribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCFURLIsExcludedFromBackupKey attribute.
For more information, please see Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes?.
It is necessary to revise your app to meet the requirements of the iOS Data Storage Guidelines.
I have tried setting the "do not back up" attribute as recommended in the Data Storage Guidelines, but is was rejected again.
I do not use iCloud in my app, and Settings > iCloud > etc. shows no usage at all.
I cannot use the Caches or tmp directories as the database is modified by the user after creation.
I seem to be between a rock and a hard place here with Apple not allowing this kind of app to function at all.
Has anyone had this problem and managed to overcome it?
EDIT 17-5-12
I still haven't managed to get this app approved yet. Has anyone managed to do this?
EDIT 1-7-12
My app has just been rejected again for the same reason. I am at a loss as to what to do here, as surely it is a common use scenario.
EDIT 11-9-12
App now approved - please see my solution below. I hope it can help someone else.
OK, here is the solution I managed to get approved (finally!)
This is the code for setting the Skip Backup attribute - note that it is different for 5.0.1 and below and 5.1 and above.
#include <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;
}
}
And here is my persistentStoreCoordinator
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"store.sqlite"];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent:#"store.sqlite"];
// For iOS 5.0 - store in Caches and just put up with purging
// Users should be on at least 5.0.1 anyway
if ([[[UIDevice currentDevice] systemVersion] isEqualToString:#"5.0"]) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [paths objectAtIndex:0];
NSString *oldStorePath = [storePath copy];
storePath = [cacheDirectory stringByAppendingPathComponent:#"store.sqlite"];
storeURL = [NSURL URLWithString:storePath];
// Copy existing file
if ([fileManager fileExistsAtPath:oldStorePath]) {
[fileManager copyItemAtPath:oldStorePath toPath:storePath error:NULL];
[fileManager removeItemAtPath:oldStorePath error:NULL];
}
}
// END iOS 5.0
if (![fileManager fileExistsAtPath:storePath]) {
// File doesn't exist - copy it over
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"store" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
__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();
}
[self addSkipBackupAttributeToItemAtURL:storeURL];
return __persistentStoreCoordinator;
}
Note that I made the decision to just store in Caches and put up with purging for iOS 5.0 users.
This was approved by Apple this month.
Please don't copy and paste this code without reading and understanding it first - it may not be totally accurate or optimised, but I hope it can guide someone to a solution that helps them.
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#include <sys/xattr.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Put this in your method
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *pathURL= [NSURL fileURLWithPath:documentsDirectory];
iOS5 = NO;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"5.0.1")) {
iOS5 = YES;
}
// Set do not backup attribute to whole folder
if (iOS5) {
BOOL success = [self addSkipBackupAttributeToItemAtURL:pathURL];
if (success)
NSLog(#"Marked %#", pathURL);
else
NSLog(#"Can't marked %#", pathURL);
}
}
- (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;
}
You said that you don't use iCloud. In that case, you should simply move your sqlite file to a directory with suffix .nosync. That should do it!
NSString *dataFileName = #"my.sqlite";
NSString *dataFileDirectoryName = #"Data.nosync";
NSString *documentsDirectoryPath = [self applicationDocumentsDirectory];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[documentsDirectoryPath stringByAppendingPathComponent:dataFileDirectoryName]] == NO) {
NSError *fileSystemError;
[fileManager createDirectoryAtPath:[documentsDirectoryPath stringByAppendingPathComponent:dataFileDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&fileSystemError];
if (fileSystemError != nil) {
NSLog(#"Error creating database directory %#", fileSystemError);
}
}
NSString *dataFilePath = [[documentsDirectoryPath
stringByAppendingPathComponent:dataFileDirectoryName]
stringByAppendingPathComponent:dataFileName];
// Move your file at dataFilePath location!
HTH.
I've been looking into this and have found this very interesting article on the subject : http://iphoneincubator.com/blog/data-management/local-file-storage-in-ios-5
I believe that you will continue to be rejected if you try to use the /Documents folder to store your DB file.
I would suggest you bite the bullet and use /Cache. The worst user case scenario would be that their device runs low on memory and the app gets cleaned removing the DB file. In this case when your app launches again it should copy over the bundled DB file into /Cache and the user would sync to go and grab the excess data from your remote server. That is assuming this is how your app works.
To get your app to move your DB file from /Documents to /Cache you can just tell NSFileManager to do this for you ...
#define FILE_MANAGER [NSFileManager defaultManager]
#define DOCUMENTS_PATH [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
#define CACHES_PATH [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
#define DB_DOCS_PATH [DOCUMENTS_PATH stringByAppendingPathComponent: #"database.db"]
#define DB_PATH [CACHES_PATH stringByAppendingPathComponent: #"database.db"]
if([FILE_MANAGER moveItemAtPath: DB_DOCS_PATH toPath:DB_PATH error:&error])
{
NSLog(#"SUCCESSFULLY MOVED %# to %#",DB_DOCS_PATH,DB_PATH);
}
This will prevent your existing users from using their iCloud storage unnecessarily.

Resources