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

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"];

Related

Where to store sensitive data

What is the best practice to store sensitive(not secure) data on iOS devices?
Where should I store information that user bought something in app? For example where should I store BOOL showAds variable if user bought "Remove Ads"?
I do understand that everything breakable, especially on jailbroken devices, I just asking what is the best practice.
My variants:
.plist in App Documents -- Editable using iFunBox, for example
NSUserDefaults -- Same here, I guess
Keychain -- best variant in my opinion so far
You can store you data in NSUserDefaults using base 64 encoding data to keep safe it.
The code is very simple:
NSUserDefaults *persistValues;
persistValues = [NSUserDefaults standardUserDefaults];
To set data (encoding it using base 64):
// Create NSData object
NSData *nsdata = [#"iOS Developer Tips encoded in Base64" dataUsingEncoding:NSUTF8StringEncoding];
// Get NSString from NSData object in Base64
NSString *base64Encoded = [nsdata base64EncodedStringWithOptions:0];
[persistValues setObject:base64Encoded forKey:#"some_key"];
To get data:
base64Encoded = [persistValues stringForKey:#"some_key"];
NSData *nsdataFromBase64String = [[NSData alloc]
initWithBase64EncodedString:base64Encoded options:0];
// Decoded NSString from the NSData
NSString *base64Decoded = [[NSString alloc]
initWithData:nsdataFromBase64String encoding:NSUTF8StringEncoding];
And if you data is bigger I suggest you uses web services and store it in a web server
Keychain is best option to store sensitive data.
Other than sensitive data below are 2 options :
Small data can be stored in NSUserDefault as its best way as physical file will not be available for changing data.
Bigger data can be stored in database with encryption

How to set Input Parameters when making Service Call

So I'm working on a small example on how to make a service call to a specific web service. I'm using the openweathermap.org web service. The link to this service is the following:
http://api.openweathermap.org/data/2.5/weather?q=London,uk
According to this link, I can search weather by city name. So I'm able to retrieve and NSLog the JSON data with the following code:
NSString *urlString = [NSString stringWithFormat:WXFORECAST, LOCATION];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:data options:kNilOptions error:nil];
NSLog(#"%#", json);
This does the bare minimum and retrieves the service. Now I would like to get my specific city instead of London.
If not, I would like to get JUST THE WEATHER part instead of getting the wind speed, and all that other garbage.
Here is the link for details on the API:
http://openweathermap.org/API#weather
Under that link there's a section that says this:
Restriction output:
To limit number of listed cities please setup cnt parameter api.openweathermap.org/data/2.5/find?lat=57&lon=-2.15&cnt=3
I believe this might be what I'm looking for but I don't know exactly how to use it...
All help is appreciated, thanks.
The API is accessed with a URL, try playing with this in your browser:
http://api.openweathermap.org/data/2.5/weather?q=New+York,us
See how I have a plus sign between "New" and "York"? That's to make sure the URL is valid, because they don't allow for spaces.
You have to figure out a way to get the URL you want in your variable. In your code, it's creating the URL using a format string stored in WXFORECAST. So, update that.
I don't have time to read through all of how that API works, but it's possible that you can't request that it gives you less information. But there's nothing stopping you from taking only what you need from it. It's all in that JSON dictionary, if you wanted to get the temperature your code might look like this.
NSArray *list = json[#"list"];
NSDictionary *london = list[0];
NSDictionary *main = london[#"main"];
NSString *temp = main[#"temp"];

NSFileWrapper fails when from iCloud and works from local directory

I have a problem syncing NSFileWrapper documents with iCloud. I am able to create my wrapper and save it to my ubiquitous container.
When I try to read it from the device that created it, it works. When I try to read form another device that got it from iCloud, it crashes.
Some code:
This function to add a wrapper container with a NSString
- (void) addNSString:(NSString*)_string toFileWrapper:(NSFileWrapper*)_wrapper forKey:(NSString*)_key {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_string];
if(data) {
[_wrapper addRegularFileWithContents:data preferredFilename:_key];
}
}
And then here is how I decode it:
- (id) unarchiveObjectFromWrappers:(NSDictionary*)_wrappers withKey:(NSString*)_key {
id value = nil;
NSFileWrapper *wrapper = [_wrappers valueForKey:_key];
if(wrapper) {
NSData *data = [wrapper regularFileContents];
if(data) {
value = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
}
return value;
}
The decoding part works on one device and not on the others (EXC_BAD_ACCESS when the NSKeyedUnarchiver tries to unarchive from the NSData. The NSData seems good, it has the proper length and everything but when I try to log its datas for example it crashes).
My guess is that the NSFileWrapper doesn't download its full content, only its structure and that I have to do something to make it available. But I don't know what.
Any ideas?
========
Edit:
NSURLUbiquitousItemIsDownloadedKey says that the file is downloaded BUT if I try to copy it to the sandbox it fails with this error: "The operation couldn’t be completed. Bad file descriptor"
So the file is either not uploaded properly to iCloud or not downloaded properly...
It drove me crazy too. The solution is rather simple, yet totally undocumented by Apple. You must download the file specifically. Only the file wrapper is downloaded automatically, but not its contents. That's why the check says the file exists.
Before copying the file over, call something like this:
[[NSFileManager defaultManager]startDownloadingUbiquitousItemAtURL:cloudURL error:nil];
Related: Cannot sync simple text file with iCloud (bad file descriptor)

How to use NSURLIsExcludedFromBackupKey Or kCFURLIsExcludedFromBackupKey?

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];

Encryption of contents in compiled iOS app ( IPA )

As IPA structure is just a zipped file containing compiled codes & media contents like images & audio, how can I protect the contents from being extracted and stolen by others? Is there any encryption I can add into the IPA?
This answer mentions that the application is already encrypted by the time it gets onto your users' devices: Does Apple modify iOS application executables on apps submitted to the App Store?
Sorry, that's only the application binary. The other media are not encrypted, and no, there's no way to encrypt the .ipa. You could try encrypting your images and other media on your system, providing a bunch of application code to decrypt those resources when the app runs, and then your decryption code will become a part of the encrypted application binary. You can't submit an encrypted IPA though, it needs to be the file directly output from Xcode.
In response to your comment, the one I've used in the past is CommonCrypto. You can use this crypto library as a starting point.
Simple usage example of the above:
NSError *error;
NSMutableData *encryptedData = [NSMutableData dataWithContentsOfFile:pathToEncryptedFile];
NSData *decryptedData = [RNDecryptor decryptData:encryptedData
withPassword:#"SuperSecretDecryptionKey"
error:&error];
UIImage *decryptedImage = [UIImage imageWithData:decryptedData];
IMPORTANT NOTE HERE: IF someone was to run the strings utility on your .app on a jailbroken iphone, or even on an iPhone they have filesystem access to via USB, they will get a list of all strings declared in your app. This includes "SuperSecretDecryptionKey". So you may want to use an integer, floating-point or other constant to do on-the-fly generation of a string decryption key, or make sure that the string you use to decrypt things is exactly the same as a normal system string so no-one suspects it as the true key. Security through obscurity, in this case, is advantageous.
To encrypt/decrypt *.strings files, you should encrypt the key and value strings in some manner (maybe one which gives you hexadecimal back, or any alphanumeric characters), and when you want to access a given value, say LicenceNumber, do this:
NSError *error;
NSData *unencryptedKey = [#"LicenceNumber"
dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedKey = [RNEncryptor encryptData:unencryptedKey
withSettings:kRNCryptorAES256Settings
password:#"SuperSecretEncryptionKey"
error:&error]
NSData *encryptedValue = [[NSBundle mainBundle]
localizedStringForKey:[NSString
stringWithUTF8String:[encryptedKey bytes]]
value:#"No licence"
table:#"EncryptedStringsFile"];
NSData *decryptedValue = [RNDecryptor decryptData:encryptedValue
withPassword:#"SuperSecretDecryptionKey"
error:&error];

Resources