How to use NSURLIsExcludedFromBackupKey Or kCFURLIsExcludedFromBackupKey? - ios

My App had been rejected because I save in-app purchase data in Documents folder on iPhone.
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.
But I want the user to use the data even if they are offline, so I'll use kCFURLIsExcludedFromBackupKey or NSURLIsExcludedFromBackupKey. What is the different between them?
The question is how to use any of them, and what will it return and how can I use this returned data?

NSError *error = nil;
BOOL result = [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];

Related

How to cache JSON web response locally on iOS - objective c

I am building a mobile iOS app for a web backend. I retrieve the JSON response using the following code:
NSError *error;
NSString *url_string = [NSString stringWithFormat: #"https://myURL"];
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString:url_string]];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
What would be the best/simplest way to store this data locally so that I can use a local database when internet connectivity is not available? The intention is to check web connectivity at launch of the app. If connection is available, get JSON response, parse and update local copy, if connection is not available parse the data from local storage.
Thanks in advance.
If you wish to cache the JSON data so you can still use it offline, I would write the JSON dictionary (or the data) to a file in your app's sandbox. A subfolder of the "Application Support" folder would be a good place. You don't want to use the Caches folder in this case because the files could be purged by iOS when you need them offline.
The trick is to map a given URL to a filename. You need this mapping to both save a file for a given URL and to later load the file if offline. You should be able convert a URL to a useful filename simply by converting all / characters to something else such as an underscore.
You probably don't want these files backed up when a user backups their iOS device so be sure you mark the files with the "do not backup" attribute. There are many existing question covering that topic.
The best way is CoreData and
the simplest way is NSUserDefaults
NSUserDefaults Class Reference
[[NSUserDefaults standardUserDefaults] setObject: jsonDict forKey:#"dictionaryKey"];
//...
NSDictionary * myDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:#"dictionaryKey"];

Move local Core Data to iCloud

How can I enable iCloud Core Data in an app which already uses local storage Core Data?
I've tried to use NSPersistentStoreUbiquitousContentNameKey in my persistent store options. Unfortunately, this option enables iCloud but does not transfer any of the local data to iCloud. I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either. I provide the persistent store, its URL, iCloud options, etc. and it still will not migrate the existing local data to iCloud. Here's how I'm using the method:
- (void)migratePersistentStoreWithOptions:(NSDictionary *)options {
NSError *error;
self.storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#.sqlite", self.SQLiteFileName]];
NSPersistentStore *store = [self.persistentStoreCoordinator migratePersistentStore:self.persistentStoreCoordinator.persistentStores.firstObject toURL:self.storeURL options:options withType:NSSQLiteStoreType error:&error];
if (store) NSLog(#"[CoreData Manager] Store was successfully migrated");
else NSLog(#"[CoreData Manager] Error migrating persistent store: %#", error);
}
The local storage remains separate from the iCloud storage. If possible, I'd like to move the local Core Data to iCloud without manually transferring each entity.
Any ideas? I can find lots of articles, tutorials, and posts about moving back to local storage from iCloud - but I want to move from local storage to iCloud.
Here's what you'll need to do
Create a local NSPersistentStoreCoordinator
Add your existing persistent store to that coordinator and store a reference to this new returned store.
Call that handy migratePersistStore:... providing the store from #2, a URL for the store in the documents directory with a different file name and the all important options including the NSPersistentStoreUbiquitousContentNameKey key.
Here's the code, notes in-line.
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
//This is the path to the new store. Note it has a different file name
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"TestRemote.sqlite"];
//This is the path to the existing store
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:#"Test.sqlite"];
//You should create a new store here instead of using the one you presumably already have access to
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSError *seedStoreError;
NSDictionary *seedStoreOptions = #{ NSReadOnlyPersistentStoreOption: #YES };
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:seedStoreOptions
error:&seedStoreError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"MyiCloudStore" };
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//This is using an operation queue because this happens synchronously
[queue addOperationWithBlock:^{
NSError *blockError;
[coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&blockError];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// This will be called when the migration is done
}];
}];
Note that after you do this migration, you'll need to configure the persistent store you use with your MOC with the new URL and always include the iCloudOptions above with the NSPersistentStoreUbiquitousContentNameKey key.
This was based on Apple's documentation.
After completion, you should see a new folder in your Documents folder in the simulator folder (~/Library/Application Support/iPhone Simulator/...) labeled CoreDataUbiquitySupport. Nested deep in there is your iCloud synced sqlite store.
Tada!
EDIT: Oh and make sure you have created an iCloud entitlement and included it in your bundle. You should be able to do that all within Xcode, but you can update it on the development portal too.
Take a look at this sample app which includes code to migrate a local core data store to iCloud and back again. Best read the associated docs and build the sample apps in your environment to get them working and once they are working then try and refactor your code to use a similar approach.
Feel free to send me an email for further help. Apologies for not giving you an answer here but it can be quite a complicated issue to deal with.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/

How to know if NSURLIsExcludedFromBackupKey is working?

I recently had my app rejected due to:
2.23: Apps must follow the iOS Data Storage Guidelines or they will be rejected
The reason for this is my app downloads a lot of image files from the web and writes them to the documents directory. So I do the following to try and remedy the situation:
NSURL *url = [NSURL URLWithString:stickerURL];
NSError *error = nil;
BOOL success = [url setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
if(!success)
{
NSLog(#"Error excluding %# from backup %#", [url lastPathComponent], error);
}
NSData *data = [NSData dataWithContentsOfURL:url];
My question is, how am I to know if NSURLIsExcludedFromBackupKey is working? The BOOL success is always coming back as YES. However, when I check Settings -> iCloud -> Storage & Backup -> Manage Storage -> MyiPhone5, it's making no difference in the size that is being shown underneath my app. Would this mean it's not working? If not, what can I fix in my code?
If you are checking "Manage Storage" in your device's settings, and after adding a bunch of files (eg., photos, documents, or whatever it is you're flagging to not be backed up) you notice that the backup size for you app is increasing, then the flag isn't "working" (although that shouldn't be the case, really).
One way to test it is to get rid of the key NSURLIsExcludedFromBackupKey, download a bunch of data, and then check to see if the backup size is increasing. Then, once you add the key back into your code, and once again download a bunch of data, the backup storage size should really not increase noticeably.

Is there a way to retrieve an NSPersistentStore knowing its URL?

Is there a way to retrieve an NSPersistentStore knowing its URL?
Something like:
NSURL *url = #"aaa\bbbb\ccc\xyz.sqlite"
NSPersistenStore *ps =[NSPersistentStore persistentStoreFromURL: url];
[self DoSomethingWith: ps];
** Obviously the method 'persistentStoreFromURL' doesn't exist!
Extra infos:
I know this store is loaded in some Coordinator (I don't know which one) and I have to remove it from its coordinator before migrating its data to another store. I only know the URL for this store.
I am using several coordinators at the same time. I want to avoid to loop through them and then loop again through all theirs stores to check if the store.URL is equal to url. This is the reason I am asking if it is possible to get the store directly from its url and then get its coordinator wihout all the looping.
You can get the current store from the Persistent Store Coordinator with:
NSURL *url = #"aaa\bbbb\ccc\xyz.sqlite"
NSPersistentStoreCoordinator *yourPSC = self.psc // Create or obtain reference to your psc
NSPersistentStore *ps = [yourPSC persistentStoreForURL:url];
[self DoSomethingWith: ps];
If you do not know which of your psc contain the store at url, check yourPSC.persistentStores for contains a store with same url.
Like so:
for (NSPersistentStore *store in yourPSC.persistentStores) {
if ([store.URL isEqual:url]) {
[yourPSC removePersistentStore:store error:nil];
}
}
You have to initialize a NSPersistentStoreusing the designated initializer
initWithPersistentStoreCoordinator:configurationName:URL:options:
as described in Apple documentation
You will also need a store coordinator for this.
If you want to remove a store from a coordinator, though, you will need to have access to the coordinator, otherwise there is no way of removing it. You can ask the NSPersistentStorefor its persistentStoreCoordinator though. Migration of stores is also supported, depending on what you actually want to achieve. Note that a migration to another store might cause UI problems.
If you only have an URL you will need to ask a coordinator if it is assigned to the store. I see no other way out of the box.

Can I mark Documents directory with "do not backup" attribute?

I had read that I can mark folders with "do not backup" attribute.
As I understand, in such case all contents of directory will be excluded from backups.
In our app we need to exclude from backup all files in Documents directory (the files can be added or deleted from Documents during app execution).
We need to store our files in Documents because we use "Application supports iTunes file sharing" feature.
Can we mark Documents directory with "do not backup attribute"?
Does Apple permits this?
Could this become the reason to reject our app?
According to apple
In iOS 5.0 and earlier, put files in the
/Library/Caches directory to prevent them from being
backed up
In iOS 5.0.1 and later, put files in the
/Library/Application Support directory and apply the
com.apple.MobileBackup extended attribute to them. This attribute prevents the files from being backed up to iTunes or iCloud. If you
have a large number of support files, you may store them in a custom
subdirectory and apply the extended attribute to just the directory.
As far as I know
You can not mark documents directory with do not back up attribute
1)you may mark up the individual files inside the documents directory using below code snippet
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *)filePathString {
NSURL *fileURL =
[NSURL fileURLWithPath:filePathString];
assert([[NSFileManager defaultManager]
fileExistsAtPath: [fileURL path]]);
NSError *error = nil;
BOOL success = [fileURL setResourceValue:[NSNumber numberWithBool:YES]
forKey:NSURLIsExcludedFromBackupKey
error:&error];
return success;
}
2)You may create a subdirectory inside documents folder and apply extended attribute to that.
you may set extended attribute using the below syntax.
int result = setxattr(path, attrName, myDataBytes, [myData length], 0, 0);
you can find more information on reading and writing extended attributes in the following link
I hope this helps

Resources