I read a lot of docs about this but I can't really understand how it precisely works.
I would like to save my apps data in JSON format on the disc of the phone.
I have a array of objects of this type:
#interface ObjectA : NSObject
#property (strong, nonatomic) NSMutableArray* names1;
#property (strong, nonatomic) NSMutableArray* names2;
#property (strong, nonatomic) NSMutableArray* names3;
#property (strong, nonatomic) NSMutableArray* names4;
#property (strong, nonatomic) NSString* nameObjectA;
#property (assign) int number;
By using JSONModel, how can I transforme a "NSMutableArray *ObjectA" in a JSON file and after that read this file back in the app.
Thanks.
- (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary {
if(self = [self init]) {
// Assign all properties with keyed values from the dictionary
_nameObjectA = [jsonDictionary objectForKey:#"nameAction"];
_number = [[jsonDictionary objectForKey:#"number"]intValue];
_actions1 = [jsonDictionary objectForKey:#"Action1"];
_actions2 = [jsonDictionary objectForKey:#"Action2"];
_actions3 = [jsonDictionary objectForKey:#"Action3"];
_actions4 = [jsonDictionary objectForKey:#"Action4"];
}
return self;
}
- (NSArray *)locationsFromJSONFile:(NSURL *)url {
// Create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
// Get the data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
// Now create a NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// Create a new array to hold the locations
NSMutableArray *actions = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "actions"
NSArray *array = [jsonDictionary objectForKey:#"actions"];
// Iterate through the array of dictionaries
for(NSDictionary *dict in array) {
// Create a new Location object for each one and initialise it with information in the dictionary
Action *action = [[Action alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[actions addObject:action];
}
// Return the array of actions objects
return actions;
}
The demo app that comes with JSONModel includes an example how to store your app's data via a JSONMOdel: https://github.com/icanzilb/JSONModel
Check the code in this view controller: https://github.com/icanzilb/JSONModel/blob/master/JSONModelDemo_iOS/StorageViewController.m
The logic is that you can export your model to a json string or json compliant dictionary and then save those to the disc using the standard APIs. Check the code
In ObjectA you define two methods -- toDictionary and initWithDictionary. Roughly:
-(NSDictionary*) toDictionary {
return #{#"names1":names1, #"names2":names2, #"names3":names3, #"names4":names4, #"nameObjectA":nameObjectA, #"number":#(number)};
}
- (id) initWithDictionary:(NSDictionary*) json {
self = [super init];
if (self) {
self.names1 = json[#"names1];
... etc
self.nameObjectA = json[#"nameObjectA"];
self.number = json[#"number"].intValue;
}
return self;
}
Run the dictionary created by toDictionary through NSJSONSerialization to produce an NSData and write that to a file. To read, fetch the NSData from the file, run back through NSJSONSerialization, and use initWithDictionary.
Of course, this assumes that the contents of your dictionaries are "JSON legal" -- strings, numbers, NSArrays, or other NSDictionarys.
And, if the arrays/dictionaries being initialized are mutable, one should specify the "MutableContainers" option on NSJSONSerialization.
Related
I have an interface that looks like this: #import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface AVBase : NSObject
#property NSString *portName;
#property NSString *uid;
#property NSString* port;
- (id) initWithPortName:(NSString *)portName andUID:(NSString *)uid andPort:(AVAudioSessionPort)port;
#end
NS_ASSUME_NONNULL_END
and the .m file
#implementation AVBase
- (id)initWithPortName:(NSString *)portName andUID:(NSString *)uid andPort:(AVAudioSessionPort)port
{
self = [super init];
if (self)
{
self.portName = portName;
self.uid = uid;
self.port = [port description];
}
return self;
}
#end
I want to create an array of current outputs for the AVAudioSession, so I do it like this:
NSMutableArray *myArray = [[NSMutableArray alloc] init];
AVAudioSession *session = AVAudioSession.sharedInstance;
NSArray *outputs = [[session currentRoute] outputs];
for(AVAudioSessionPortDescription* output in outputs)
{
AVBase* av = [AVBase alloc];
av = [av initWithPortNumber:output.portName andUID:output.UID andPort:output.portType];
[myArray addObject:av];
}
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myArray options:NSJSONWritingPrettyPrinted error:&error];
But when I try to serialize myArray I get an error that says:
Exception 'Invalid type in JSON write (AVBase)
I don't understand what's wrong, all the properties in my class are of type NSString so it should work.
NSJSONSerialization accepts only NSArray, NSDictionary, NSString, NSNumber (and NSNull), for its top level, but all sublevels/subproperties too.
myArray is a NSArray of AVBase, and AVBase isn't one of them.
You need to convert an AVBase into a NSDictionary first.
-(NSDictionary *)toDict {
return #{#"portName": portName, #"uid": uid, #"port": port};
}
Then:
[myArray addObject:[av toDict]];
If you don't use AVBase, or just for it, you can construct the NSDictionary directly from AVAudioSessionPortDescription *output, no need to use the AVBase here.
I have a property:
#property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, NSString *>*>*mainDict;
So it is a dictionary with another dictionary as its value.
Now, when I wanna populate my dictionary, I do:
NSData *data = [json_string dataUsingEncoding:NSUTF8StringEncoding];
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
for (NSString *key in [[json objectForKey:#"countries"] allKeys]) {
NSDictionary *innerDictionary = [json objectForKey:#"countries"][key];
[mainDict setObject:innerDictionary forKey:key];
}
when I debug, I see that my innerDictionary is correct and has its values but somehow my mainDict is not being filled.
Could you please tell me what I do wrong?
Declaring mainDict as a #property will create an instance variable in your class, but that variable will be nil.
You'll need to initialize the mainDict somewhere before your population code gets called, perhaps in your -init, -viewDidLoad, or somewhere appropriate for your app's architecture.
mainDict initialization should look like:
mainDict = [[NSMutableDictionary alloc] init];
It's a custom class:
#import <Foundation/Foundation.h>
#interface timeTable : NSObject
#property (nonatomic) int ID;
#property (nonatomic) NSString * type;
#property (nonatomic) NSString * time;
#property (nonatomic) NSString * busno;
#property (nonatomic) NSString * stops;
// nothing is done in it's .m file not even synthesise
// thats an other class
#import <Foundation/Foundation.h>
#import "timeTable.h"
#interface refreshDatabase : NSObject
#property (strong, nonatomic) NSMutableArray * arrayTimeTable;
#property (strong, nonatomic) timeTable * objectTimeTable;
// in it's .m file i am downloading a JSON formatted array using a
service then i am saving it to NsMutbaleArray
// downloading a json array which contains a rows of data
NSError * error;
NSArray * jsonArray = [NSJSONSerialization JSONObjectWithData:
[safeString dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingAllowFragments error:&error];
NSLog(#"json Array %#", jsonArray);
// for getting an instance of array
NSDictionary * jsonElement;
for (int i=0; i<jsonArray.count ; i++)
{ // each row will be saved in an object of timetable class then that
// object will be saved to nsmutablearray
jsonElement = [jsonArray objectAtIndex:i];
objectTimeTable = [[timeTable alloc]init];
objectTimeTable.ID = [[jsonElement objectForKey:#"id"]intValue];
objectTimeTable.type = [jsonElement objectForKey:#"type"];
objectTimeTable.time = [jsonElement objectForKey:#"time"];
objectTimeTable.busno = [jsonElement objectForKey:#"busno"];
objectTimeTable.stops = [jsonElement objectForKey:#"stops"];
// adding an instance from JSON Array to our NSmutablearray
[arrayTimeTable addObject:objectTimeTable];
}//end of json Array FOR loop
// our array containnig all the objects will be saved using
//NSUserDefualts
// userDefaults is an object of NSUserDefaults
if(userDefaults)
{ // its not saving it to userdefaults
[userDefaults setObject:arrayToStore forKey:#"ArrayOfTimeTables"];
[userDefaults synchronize];
}
// retrieving the saved array from NSUSerDefaults and printing it
// using slog
timeTable *objPrint = [[timeTable alloc]init];
NSMutableArray *arrayLoader = [userDefaults arrayForKey:#"ArrayOfTimeTables"];
for (int i=0; i<arrayLoader.count ; i++)
{
objPrint = [arrayLoader objectAtIndex:i];
NSLog(#"outSide Printing For LOOP After Loading of tim # %d times havind id =%d type = %# time = %# busno = %# stops = %#",i,objPrint.ID,objPrint.type,objPrint.time,objPrint.busno,objPrint.stops);
}
Thanx a lot in helping me in advance.
Please tell me how to save that array which contains object of timetable class into nsUseDefaults and then how to load it back.
Please help me. I read a lot of similar question and answers, but don't know how to make them work for me.
Use NScoding to encode each of your custom object then add that custom object into an array then encode other and then add it to the array then save that array into NSUserDefaults
encoding and decoding of upper given question
is
the custom class .h file
#import <Foundation/Foundation.h>
#interface timeTable : NSObject<NSCoding>
#property (nonatomic) NSString * ID;
#property (nonatomic) NSString * type;
#property (nonatomic) NSString * time;
#property (nonatomic) NSString * busno;
#property (nonatomic) NSString * stops;
the custom class .m file
#import "timeTable.h"
#implementation timeTable
#synthesize ID;
#synthesize type;
#synthesize time;
#synthesize busno;
#synthesize stops;
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.ID forKey:#"ID"];
[aCoder encodeObject:self.type forKey:#"type"];
[aCoder encodeObject:self.time forKey:#"time"];
[aCoder encodeObject:self.busno forKey:#"busno"];
[aCoder encodeObject:self.stops forKey:#"stops"];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
if((self = [super init])) {
//decode properties, other class vars
self.ID = [aDecoder decodeObjectForKey:#"ID"];
self.type = [aDecoder decodeObjectForKey:#"type"];
self.time = [aDecoder decodeObjectForKey:#"time"];
self.busno = [aDecoder decodeObjectForKey:#"busno"];
self.stops = [aDecoder decodeObjectForKey:#"stops"];
}
return self;
}
#end
where you encode each custom object one by one and adding it to the array then save that NSMutableArray or NSArray
into NSUserDefaults
encoding a custom object then adding it to array and saving it into user defaults
// encoding a custom object before saving it to array
NSData *encodeTimeTableObj = [NSKeyedArchiver
archivedDataWithRootObject:objectTimeTable];
addObject:encodeTimeTableObj];
//saving it to user Defaults
if(userDefaults)
{
[userDefaults setObject:arrayTimeTable
forKey:#"ArrayOfTimeTables"];
[userDefaults synchronize];
NSLog(#"saving to usedefaults");
}
retriving an array either mutable or non mutable then decoding each of its object
NSMutableArray *arrayLoader = [userDefaults
objectForKey:#"ArrayOfTimeTables"];
NSData * decode = [arrayLoader objectAtIndex:0];
// in case of upper given custom class Time Table
timeTable *objPrint = [NSKeyedUnarchiver unarchiveObjectWithData:decode];
Use NSArray to get array from NSUSerDefaults as NSUSerDefaults return immuttable array.
If you need NSMutableArray, then convert this NSArray to NSMutableArray.
// retrieving the saved array from NSUSerDefaults and printing it
// using slog
timeTable *objPrint = [[timeTable alloc]init];
NSArray *arrayLoader = [userDefaults arrayForKey:#"ArrayOfTimeTables"];
for (int i=0; i
In order to test the JSON handling of my app I have created a test.json file that I want to load into an UITableView in my UIViewController class. I have created the JSON file and made a separate json loading class (JSONLoader) that implements the code:
#import <Foundation/Foundation.h>
#interface JSONLoader : NSObject
//return an array of chat objects from the json file given by url
- (NSArray *)chattersFromJSONFile:(NSURL *)url;
#end
in the .h file, in the .m file I have:
#import "JSONLoader.h"
#import "Chatter.h"
#implementation JSONLoader
- (NSArray *)chattersFromJSONFile:(NSURL *)url {
//create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
//get data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//create NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//create new array to hold chatter information
NSMutableArray *localChatters = [[NSMutableArray alloc] init];
//get an Array of dictionaries with the key "chatters"
NSArray *chatterArray = [jsonDictionary objectForKey:#"chatters"];
//iterate through array of dictionaries
for(NSDictionary *dict in chatterArray) {
//create new chatter object for each one and initialize it with info from the dictionary
Chatter *chatter = [[Chatter alloc] initWithJSONDictionary:dict];
//add the chatter to an array
[localChatters addObject:chatter];
}
//return array
return localChatters;
}
#end
Which I believe will work for both a JSON file loaded from a URL (the end goal) and also a JSON file I have in my Xcode project as a test. In the -viewDidLoad of my viewController.m file I use:
//create a new JSONLoader with a local file from URL
JSONLoader *jsonLoader = [[JSONLoader alloc] init];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"test" withExtension:#"json"];
NSLog(#"%#",url);
//load the data on a background queue
//use for when connecting a real URL
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_localChatters = [jsonLoader chattersFromJSONFile:url];
NSLog(#"%#",_localChatters);
//push data on main thread (reload table view once JSON has arrived)
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
I import the JSONLoader file and also the class the represents a test JSON object (singular chatter) and in my implementation I declare a NSArray *_localChatters.
I'm pretty convinced this should work, however when I NSLog(...) the array it displays empty (), where it should have a list of class objects. This means the JSON is never being parsed by my JSONLoader in the first place.
Any particular reason this could happen?
I have the following code:
NSDictionary *dict = #[#{#"Country" : #"Afghanistan", #"Capital" : #"Kabul"},
#{#"Country" : #"Albania", #"Capital" : #"Tirana"}];
I want to list many countries and capitals, and then randomize i.e a country and put it on the screen, then the user should be able to pick the correct capital..
How to I put the Country? Like dict.Country[0] or something like that?
What is wrong with the code? I get the error "Initializer element is not a compile-time constant" and the warning "Incompatible pointer types initializing 'NSDictionary *_strong' with an expression of type 'NSArray *'.
Can I make a third String in the Dictionary, containing a flag file.. for example
#"Flagfile" : #"Albania.png"
and later put it in a image view?
I want like a loop with a random number I (for example) and put like (I know this is not right, but I hope you get the point)
loop..
....
text= dict.Country[I];
button.text= dict.Capital[I];
Imageview=dict.Flagfile[I];
.....
....
Your top level element is an NSArray (#[], with square brackets, makes an array) of two NSDictionary's. To access an attribute in one of the dictionaries, you would do array[index][key], e.g. array[0][#"Country"] would give you #"Afghanistan". If you did NSArray *array = ... instead of NSDictionary *dict = ...
If you want to pick a country at random, you can get a random number, get it mod 2 (someInteger % 2) and use that as your index, e.g. array[randomNumber % 2][#"Country"] will give you a random country name from your array of dictionaries.
If you store an image name in the dictionaries, you can load an image of that name using UIImage's +imageNamed: method.
Here's more complete instruction on mbuc91's correct idea.
1) create a country
// Country.h
#interface Country : NSObject
#property(strong,nonatomic) NSString *name;
#property(strong,nonatomic) NSString *capital;
#property(strong,nonatomic) NSString *flagUrl;
#property(strong,nonatomic) UIImage *flag;
// this is the only interesting part of this class, so try it out...
// asynchronously fetch the flag from a web url. the url must point to an image
- (void)flagWithCompletion:(void (^)(UIImage *))completion;
#end
// Country.m
#import "Country.h"
#implementation Country
- (id)initWithName:(NSString *)name capital:(NSString *)capital flagUrl:(NSString *)flagUrl {
self = [self init];
if (self) {
_name = name;
_capital = capital;
_flagUrl = flagUrl;
}
return self;
}
- (void)flagWithCompletion:(void (^)(UIImage *))completion {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.flagUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
completion(image);
} else {
completion(nil);
}
}];
}
#end
2) Now, in some other class, use the Country
#import "Country.h"
- (NSArray *)countries {
NSMutableArray *answer = [NSMutableArray array];
[answer addObject:[[Country alloc]
initWithName:#"Afghanistan" capital:#"Kabul" flagUrl:#"http://www.flags.com/afgan.jpg"]];
[answer addObject:[[Country alloc]
initWithName:#"Albania" capital:#"Tirana" flagUrl:#"http://www.flags.com/albania.jpg"]];
return [NSArray arrayWithArray:answer];
}
- (id)randomElementIn:(NSArray *)array {
NSUInteger index = arc4random() % array.count;
return [array objectAtIndex:index];
}
-(void)someMethod {
NSArray *countries = [self countries];
Country *randomCountry = [self randomElementIn:countries];
[randomCountry flagWithCompletion:^(UIImage *flagImage) {
// update UI, like this ...
// self.flagImageView.image = flagImage;
}];
}
You cannot initialize an NSDictionary in that way. An NSDictionary is an unsorted set of key-object pairs - its order is not static, and so you cannot address it as you would an array. In your case, you probably want an NSMutableDictionary since you will be modifying its contents (see Apple's NSMutableDictionary Class Reference for more info).
You could implement your code in a few ways. Using NSDictionaries you would do something similar to the following:
NSMutableDictionary *dict = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:#"Afghanistan", #"Country",
#"Kabul", #"Capital", nil];
You would then have an array of dictionaries, with each dictionary holding the details of one country.
Another option would be to create a simple model class for each country and have an array of those. For example, you could create a Class named Country, with Country.h as:
#import <Foundation/Foundation.h>
#interface Country : NSObject
#property (nonatomic, retain) NSString *Name;
#property (nonatomic, retain) NSString *Capital;
//etc...
#end