iOS persistent storage strategy - objective-c

I'm developing an app which will save data to the local file system. The data that will be saved will be mostly NSString and NSDate. The data will not be saved that often, perhaps new data will be entered 10 times at a typical usage. The data should also of course be retrievable (CRUD)
How should I save this data? First of all is it necessary to model these objects? If not should I use property lists? Or SQLLite3?
Else should I archive the class models? Use SQLLite3?
EDIT: I accidentally left out some vital information about the app. Actually my app will be having 2 data models which have an aggregated relationship. So my first data model (lets call it DataA) which will have a NSString and NSDate will also have a reference to the second data model (lets call it DataB) which itself will consist of a NSString and a NSArray. So it gets a little more complicated now. If an object from DataB gets deleted it should of course cease to exist in DataA (but the rest of DataA should be left untouched)

This kind of data seems to be very simple to store and retrieve and does not have any other dependencies such as a horridly complex object graph.
You should store this data in a flat file or in NSUserDefaults.
I'll give you an example of both, using object archiving with the use of the NSCoding protocol:
#interface ApplicationData <NSCopying, NSCoding> {}
#property (nonatomic, strong) NSDate *someDate;
#property (nonatomic, strong) NSDate *someOtherDate;
#property (nonatomic, copy) NSString *someString;
#property (nonatomic, copy) NSString *someOtherString;
#end
#implementation ApplicationData
#synthesize someDate = _someDate, someOtherDate = _someOtherDate, someString = _someString, someOtherString = _someOtherString;
- (NSArray *)keys {
static dispatch_once_t once;
static NSArray *keys = nil;
dispatch_once(&once, ^{
keys = [NSArray arrayWithObjects:#"someString", #"someOtherString", #"someDate", #"someOtherDate", nil];
});
return keys;
}
- (id) copyWithZone:(NSZone *) zone {
ApplicationData *data = [[[self class] allocWithZone:zone] init];
if(data) {
data.someString = _someString;
data.someOtherString = _someOtherString;
data.someDate = _someDate;
data.someOtherDate = _someOtherDate;
//...
}
return data;
}
- (void) encodeWithCoder:(NSCoder *) coder {
[super encodeWithCoder:coder];
NSDictionary *pairs = [self dictionaryWithValuesForKeys:[self keys]];
for(NSString *key in keys) {
[coder encodeObject:[pairs objectForKey:key] forKey:key];
}
}
- (id) initWithCoder:(NSCoder *) decoder {
self = [super initWithCoder:decoder];
if(self) {
for(NSString *key in [self keys]) {
[self setValue:[decoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
#end
Then, say in your application delegate, you can do this:
#interface AppDelegate (Persistence)
#property (nonatomic, strong) ApplicationData *data;
- (void)saveApplicationDataToFlatFile;
- (void)loadApplicationDataFromFlatFile;
- (void)saveApplicationDataToUserDefaults;
- (void)loadApplicationDataFromUserDefaults;
#end
#implementation AppDelegate (Persistence)
#synthesize data;
- (NSString *)_dataFilePath {
static NSString *path = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) stringByAppendingPathComponent:#"xAppData.dat"];
});
return path;
}
- (void)loadApplicationDataFromUserDefaults {
NSData *archivedData = [[NSUserDefaults standardUserDefaults] objectForKey:#"appData"];
self.data = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
}
- (void)saveApplicationDataToUserDefaults {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:self.data];
[[NSUserDefaults standardUserDefaults] setObject:archivedData forKey:#"appData"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)loadApplicationDataFromFlatFile {
NSData *archivedData = [NSData dataWithContentsOfFile:[self _dataFilePath]];
self.data = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
}
- (void)saveApplicationDataToFlatFile {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:self.data];
[archivedData writeToFile:[self _dataFilePath] atomically:YES];
}
#end
Disclaimer: I have not tested this code.

NSUserDefault is good choice if you have only a limited set of data.
But if you consider filtering and fetching, then you should have a look to Core Data to store objects.
Even if the object graph is minimal you will gain cached object management and free undo/redo management.

I would suggest that you go with NSUserDefaults. It's surely the best approach to store information pertaining to your app and any app related data. Check the documentation !!

Related

Can't retrieve data after storing to disk using NSKeyedArchiver

I'm storing a custom object in a dictionary which i'm then saving to disk using NSKeyedArchiver. In the same method where I save the data, I do a quick test at the end to make sure I can load the data but everything comes out null. Why is this happening? I've been following the instructions in this tutorial: http://cocoadevcentral.com/articles/000084.php
/** Interface of viewcontroller**/
#import "User.h"
#interface BWViewController : UIViewController
{
IBOutlet UITextField *userNameField;
IBOutlet UITextField *passwordField;
IBOutlet UILabel * loginStatus;
}
#property (nonatomic, copy) NSString *holdPassword, *holdUserName;
-(IBAction)signUpButton;
-(IBAction)loginButton;
-(NSString*) pathForDataFile;
-(void) saveDataToDisk:(User*) someUser;
-(User *) loadDataFromDisk:(NSString*) theKey;
#end
/**Implementation file **/
#import "BWViewController.h"
#interface BWViewController ()
#end
#implementation BWViewController
#synthesize holdPassword,holdUserName;
-(IBAction)signUpButton
{
User * firstUser = [[User alloc] init];
firstUser.userName = userNameField.text;
firstUser.password = passwordField.text;
/**To save to file**/
[self saveDataToDisk:firstUser];
loginStatus.text = #"Thanks for signing up";
}
-(NSString*) pathForDataFile
{
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString *folder =#"~/Library/Application Support/signUpApp/";
folder = [folder stringByExpandingTildeInPath];
if([fileManager fileExistsAtPath:folder] == NO)
{
[fileManager createFileAtPath:folder contents:nil attributes:nil];
}
NSString *fileName = #"signUpApp.demo";
return [folder stringByAppendingPathComponent:fileName];
}
-(void) saveDataToDisk:(User*) someUser
{
User * savingUser = someUser;
NSString * path = [self pathForDataFile];
NSMutableDictionary * rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:savingUser forKey:userNameField.text];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path];
NSLog(#"saving user to disk %#", savingUser.userName);
/**Testing the load process**/
User * testLoadUser;
NSMutableDictionary *unRootObject;
unRootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
testLoadUser = [unRootObject valueForKey:userNameField.text];
NSLog(#"Testing the unarchive %# and %#", testLoadUser.userName, testLoadUser.password);
}
/**User implementation file**/
#import "User.h"
#implementation User
#synthesize userName, password;
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject: userName forKey:#"username"];
[coder encodeObject: password forKey:#"password"];
}
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
self.userName = [coder decodeObjectForKey:#"username"];
self.password = [coder decodeObjectForKey:#"password"];
}
return self;
}
#end
This looks like iOS code, but you're using a Mac OS X convention for determining the folder for the path. In iOS, you should use the Documents folder.
For iOS, the path for the filename in the Documents folder should be:
- (NSString*) pathForDataFile
{
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingString:#"signUpApp.demo"];
return path;
}
I'd also suggest checking the return code from archiveRootObject method, to confirm whether the archive succeeded or not. No point in going further if that failed.
Also, in your old pathForDataFile, you're calling createFileAtPath:contents:attributes:, but I suspect you intended createDirectoryAtURL:withIntermediateDirectories:attributes:error:. The former is for creating a file, and the latter is for creating a folder. And your code is trying to create a folder.
Quick things you can check..
1) IS file is Actually created on the location ?
2) Is there any data (random chars) in that file ?
3) Is your code contains <NSCopying> protocol in your user.h class?
4) Make sure you don't have - (id) initWithCoder: (NSCoder *)coder and - (void) encodeWithCoder: (NSCoder *)coder in your header file.

How to load an Objective C Singleton using NSCoding?

I am writing a basic game, I am using a GameStateManager which is a singleton and handles all state management, such as saves, loads, delete methods.
I have a separate singelton which handles all the Game stuff. The game object is inside the game state manager so I can save it out using NSCoding and Archiving.
When I save the state there appears to be no issues and the Game object (singleton) is saved properly.
However, when I try to load the state (by relaunching the app), the game object is always null.
Strangley, if I remove the singleton properties and make it a standard class, this issue goes away and I can load the class and all its properties without any major issues.
In summary, I do this:
GameStateManager = Singleton, handles all game state management (load, save) and has a game object (game)
Game = Singleton which handles things within the game and has NSCoding protocol employed.
Saving the game state with the game object is fine, the object is clearly there.
Loading the game state seems to make the game object null. It should be there, but for some reason it never loads it.
If I remove all the properties that make the game class a singelton and make it a normal class, the issue seems to go away.
I think it has something to do with the fact that Game is never init'd, but this does not make sense because I can get Game to load when it has no singleton properties.
Code now follows.
// GameStateManager.m
-(void)saveGameState
{
CCLOG(#"METHOD: saveGameState()");
self.lastSaveDate = [NSDate date];
NSMutableData *data;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
NSKeyedArchiver *archiver;
BOOL result;
data = [NSMutableData data];
archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[archiver encodeObject:self.game forKey:#"game"];
[archiver finishEncoding];
result = [data writeToFile:archivePath atomically:YES];
[archiver release];
}
-(void)loadGameState
{
CCLOG(#"METHOD: loadGameState()");
NSData *data;
NSKeyedUnarchiver *unarchiver;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
data = [NSData dataWithContentsOfFile:archivePath];
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
// Customize unarchiver here
self.game = [unarchiver decodeObjectForKey:#"game"];
self.lastSaveDate = [unarchiver decodeObjectForKey:#"lastSaveDate"];
[unarchiver finishDecoding];
[unarchiver release];
CCLOG(#"Last Save Date = %#", self.lastSaveDate);
NSLog(#"game = %#", self.game);
}
// END OF GAMESTATEMANAGER
// -------------------------
// Game.h
#interface Game : NSObject
<NSCoding>
{
NSMutableArray *listOfCities;
NSMutableArray *listOfColors;
NSMutableArray *listOfPlayers;
}
#property (nonatomic, retain) NSMutableArray *listOfCities;
#property (nonatomic, retain) NSMutableArray *listOfColors;
#property (nonatomic, retain) NSMutableArray *listOfPlayers;
+(Game *) sharedGame;
//
// Game.m
// This is a cut-down version of the game object
// Game is a singelton
// The listOfCities, etc are arrays
//
#implementation Game
SYNTHESIZE_SINGLETON_FOR_CLASS(Game)
#synthesize listOfCities, listOfPlayers;
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
self.listOfCities = [aDecoder decodeObjectForKey:#"listOfCities"];
self.listOfPlayers = [aDecoder decodeObjectForKey:#"listOfPlayers"];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
// Archive objects
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
[aCoder encodeObject:self.listOfCities forKey:#"listOfCities"];
[aCoder encodeObject:self.listOfPlayers forKey:#"listOfPlayers"];
}
#end
My question is, how do I successfully save and load an Objective C singelton using NSCoding, and the Archiver?
Edit;
I have tried:
// In the GameStateManager
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.lastSaveDate = [[decoder decodeObjectForKey:#"lastSaveDate"] retain];
self.game = [[decoder decodeObjectForKey:#"game"] retain];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[encoder encodeObject:self.game forKey:#"game"];
}
and
// In Game.m
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
Since you're dealing with a singleton, you only ever want a single instance of the class to exist at any time. So you will want to archive and unarchive that single instance only.
Consider this code (assumes ARC is enabled):
#interface Singleton : NSObject <NSCoding>
+ (id)sharedInstance;
#property (strong, nonatomic) NSString *someString;
#end
#implementation Singleton
+ (id)sharedInstance {
static Singleton instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Singleton alloc] init];
});
return instance;
}
#pragma mark - NSCoding implementation for singleton
- (id)initWithCoder:(NSCoder *)aDecoder {
// Unarchive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[instance setSomeString:[aDecoder decodeObjectForKey:#"someStringKey"]];
return instance;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
// Archive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[aCoder encodeObject:[instance someString] forKey:#"someStringKey"]];
}
#end
Using this template, you can archive/unarchive the singleton, like this:
// Archiving calls encodeWithCoder: on the singleton instance.
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:[Singleton sharedInstance]] forKey:#"key"];
// Unarchiving calls initWithCoder: on the singleton instance.
[[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"key"]];
try this in your initWithCoder method :
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
Easiest way:
1. Load singleton:
+ (AppState *)sharedInstance
{
static AppState *state = nil;
if ( !state )
{
// load NSData representation of your singleton here.
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"appStateData"];
if ( data )
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[AppState alloc] init];
}
}
return state;
}
2. Save singleton on a disk
- (BOOL)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
// now save NSData to disc or NSUserDefaults.
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"appStateData"];
}
3. Implement NSCoding methonds:
- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
Done!
I'm using a hybrid approach encompassing answers by Eric Baker and skywinder.
+ (GameStateManager *)sharedInstance {
static GameStateManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"key"];
if (data) {
instance = [NSKeyedUnarchiver unarchiveObjectWithData:data];
} else {
instance = [[GameStateManager alloc] init];
}
});
return instance;
}

Storing custom objects in NSUserDefaults

I am trying to save an object in NSUserDefaults but i have not been successful in it. Scenario is that, I have an object syncObject of class SyncObjct. I have implemented following methods in SyncObject class
- (NSMutableDictionary*)toDictionary;
+ (SyncObject*)getFromDictionary :(NSMutableDictionary*)dictionary;
- (id) initWithCoder: (NSCoder *)coder;
- (void) encodeWithCoder: (NSCoder *)coder;
And also the SyncObject class is NSCoding class. Now SyncObject class contains an NSMutableArray with name jobsArray. This array contains objects of class JobsObject. I implemented same above methods for this class as well to make it coding compliant. In toDictionary method of SyncObject class, i am storing this array in dictionary by writing following line.
[dictionary setValue:jobsArray forKey:#"jobsArray"];
and I am retrieving it in getFromDictionary method of SyncOject class by writing following line.
+ (SyncObject*)getFromDictionary :(NSMutableDictionary*)dictionary
{
NSLog(#"SyncObject:getFromDictionary");
SyncObject *syncObject = [[SyncObject alloc] init];
#try
{
syncObject.noOfJobs = [[dictionary valueForKey:#"noOfJobs"] intValue];
syncObject.totalJobsPerformed = (TotalJobsPerformed*)[dictionary valueForKey:#"totalobsPerformed"];
syncObject.jobsArray = [dictionary valueForKey:#"jobsArray"];
}
#catch (NSException * e)
{
NSLog(#"EXCEPTION %#: %#", [e name], [e reason]);
}
#finally
{
}
return syncObject;
}
And also I am storing syncObject in NSUserDefault by writing following lines.
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:syncObject];
[userStorage setObject:myEncodedObject forKey:SYNC_OBJECT];
I am retrieving the object from NSUserDefaults by writing following lines
NSData *myEncodedObject = [userStorage objectForKey: SYNC_OBJECT];
syncObject = (SyncObject*)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
Problem is that i am not able to retrieve jobsArray correctly. Some invalid object is returned and app crashes when trying to access it. Please can anyone tell me the reason of this problem?
Best Regards
I believe the problem is that NSUserDefaults does not support storing of custom object types.
From Apple's documentation when using -setObject: The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You can review the remaining documentation directly from Apple's NSUserDefault Class Reference manual.
Custom class .h
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property(nonatomic, retain) NSArray *names;
#property(strong,nonatomic)NSString *name;
#end
custom class .m
#import "Person.h"
#implementation Person
#synthesize names;
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:#"name"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.name = [decoder decodeObjectForKey:#"name"];
}
return self;
}
#end
To create Person class object and store and retrieve your stored data from NSUserDefaults
Stored your object :
#import "ViewController.h"
#import "Person.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UITextField *textBox;
#end
NSMutableArray *arr;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)saveButton:(id)sender {
Person *person =[Person new];
person.name = _textBox.text;
arr = [[NSMutableArray alloc]init];
[arr addObject:person];
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:arr];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:encodedObject forKey:#"name"];
[defaults synchronize];
}
- (IBAction)showButton:(id)sender {
_label.text = #"";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *storedEncodedObject = [defaults objectForKey:#"name"];
NSArray *arrStoreObject = [NSKeyedUnarchiver unarchiveObjectWithData:storedEncodedObject];
for (int i=0; i<arrStoreObject.count; i++)
{
Person *storedObject = [arrStoreObject objectAtIndex:i];
_label.text = [NSString stringWithFormat:#"%# %#", _label.text , storedObject.name];
}
}
#end

Objective-C for Dummies: How do I loop through an NSDictionary inside of an NSDictionary?

Alright guys, I'm quite confused. So, I have an NSDictionary which is populated by a JSON string which looks like:
{"Success":true,"Devices":[{"UDId":"...","User":"...","Latitude":0.0,"Longitude":0.0}]}
Now, I know how to check if Success is true, but I need to loop through the array of Devices (JSON object) and create an internal array of Devices (internal app object) and I have no idea how to do that. Can someone please explain how to do it?
Here's my Device.m/h:
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#interface Device : NSObject {
NSString *udId;
NSString *name;
NSNumber *latitude;
NSNumber *longitude;
}
#property (nonatomic, retain) NSString *udId;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#pragma mark -
#pragma mark MKAnnotation Properties
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
----
#import "Device.h"
#implementation Device
#synthesize udId, name, latitude, longitude;
- (CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D internalCoordinate;
internalCoordinate.latitude = [self.latitude doubleValue];
internalCoordinate.longitude = [self.longitude doubleValue];
return internalCoordinate;
}
- (void)dealloc {
[udId release];
udId = nil;
[name release];
name = nil;
[latitude release];
latitude = nil;
[longitude release];
longitude = nil;
[super dealloc];
}
#end
And here's the methods where I should be reading the response and converting it to objects I can use:
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
I'd really appreciate some help on this. I just can't seem to wrap my head around it...
Thanks in advance!
You are almost there. In your code where you say:
// READ "DEVICES" AND CONVERT TO OBJECTS
do this:
NSArray * devices = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary * deviceInfo in devices) {
Device * d = [[[Device alloc] init] autorelease];
[d setLatitude:[deviceInfo objectForKey:#"Latitude"]];
[d setLongitude:[deviceInfo objectForKey:#"Longitude"]];
[d setName:[deviceInfo objectForKey:#"User"]];
[d setUdId:[deviceInfo objectForKey:#"UDId"]];
// do some stuff with d
}
What's going on here: I didn't see what JSON library you are using to convert, but presuming it works like TouchJSON or SBJSON, the JSON array is automatically turned into an NSArray instance, while the inner hashes of the NSArray are NSDictionary objects. At the point that you have deserialized that JSON string, everything you're dealing with will be instances of NSString, NSNumber, NSArray and NSDictionary (and depending on the library, NSNull to represent null values).
First you need to define your initializer/constructor for your Device class.
Device.h
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon;
Device.m
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon {
if (self = [super init]) {
self.udid = udid;
self.name = name;
self.latitude = lat;
self.longitude = lon;
}
return self;
}
Then you can initialize a new object like:
Device *dev = [[Device alloc] initWithUdid:#"a udid" name:#"the name" latitude:latNum longitude:lonNum];
So, you should be able to iterate the array and build your Device objects like so:
NSArray *devicesArray = [dict objectForKey:#"Devices"];
for (NSDictionary *d in devicesArray) {
Device *dev = [[Device alloc] initWithUdid:[d objectForKey:#"UDId"]
name:[d objectForKey:#"User"]
latitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]
longitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]];
}
You want to access the array of device dictionaries from the top-level dictionary just as you did the Success value. Then iterating over the dictionaries you can use each's -keyEnumerator method to iterate over its keys.
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
NSArray* deviceArray = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary* dict in deviceArray)
{
for(NSString* key in [dict keyEnumerator])
{
NSLog(#"%# -> %#", key, [dict objectForKey:key]);
}
}
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
Sounds like you need to reuse your line:
[jsonDictionary objectForKey:#"Success"]
try having a look at
[jsonDictionary objectForKey:#"Devices"]
You really need to figure out what type it returns.
If you're lucky, it returns an NSDictionary, or alternately something that you can easily turn into an NSDictionary.

Save / Write NSMutableArray of objects to disk?

Initially I thought this was going to work, but now I understand it won't because artistCollection is an NSMutableArray of "Artist" objects.
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
}
My question is what is the best way of recording to disk my NSMutableArray of "Artist" objects so that I can load them the next time I run my application?
artistCollection = [[NSMutableArray alloc] init];
newArtist = [[Artist alloc] init];
[newArtist setFirName:objFirName];
[newArtist setSurName:objSurName];
[artistCollection addObject:newArtist];
NSLog(#"(*) - Save All");
[artistCollection writeToFile:#"/Users/Fgx/Desktop/stuff.txt" atomically:YES];
EDIT
Many thanks, just one final thing I am curious about. If "Artist" contained an extra instance variable of NSMutableArray (softwareOwned) of further objects (Applications) how would I expand the encoding to cover this? Would I add NSCoding to the "Applications" object, then encode that before encoding "Artist" or is there a way to specify this in "Artist"?
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
NSMutableArray *softwareOwned;
}
#interface Application : NSObject {
NSString *appName;
NSString *appVersion;
}
many thanks
gary
writeToFile:atomically: in Cocoa's collection classes only works for property lists, i.e. only for collections that contain standard objects like NSString, NSNumber, other collections, etc.
To elaborate on jdelStrother's answer, you can archive collections using NSKeyedArchiver if all objects the collection contains can archive themselves. To implement this for your custom class, make it conform to the NSCoding protocol:
#interface Artist : NSObject <NSCoding> {
NSString *firName;
NSString *surName;
}
#end
#implementation Artist
static NSString *FirstNameArchiveKey = #"firstName";
static NSString *LastNameArchiveKey = #"lastName";
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
firName = [[decoder decodeObjectForKey:FirstNameArchiveKey] retain];
surName = [[decoder decodeObjectForKey:LastNameArchiveKey] retain];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:firName forKey:FirstNameArchiveKey];
[encoder encodeObject:surName forKey:LastNameArchiveKey];
}
#end
With this, you can encode the collection:
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
Take a look at NSKeyedArchiver. Briefly :
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
You'll need to implement encodeWithCoder: on your Artist class - see Apple's docs
Unarchiving (see NSKeyedUnarchiver) is left as an exercise for the reader :)