How to determine if plist contains NSDictionary or NSMutableArray? - iphone

I may be phrasing the question incorrectly based on my situation - sorry if that's the case.
Here's the issue: In a previous version of my app, I'm saving an NSMutableArray to a plist using NSCoding.
In a new version, I've added settings data, so I now put the previous array and the new data in an NSDictionary then use NSCoding. Works fine.
However, for this release, I'll have to determine if an existing plist is the old version (NSMutableArray) or the new one (NSDictionary).
The basic code is this:
NSData *codedData=[[NSData alloc] initWithContentsOfFile:plistPath];
//EITHER this (array): allMsgs=[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
//OR this (nsdict): tmpdict=(NSDictionary *)[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
How can I read the plist and determine if it's an NSDictionary or NSMutableArray without throwing errors?

Just store the unarchived object as an id, then use isKindOfClass: to check its class:
id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:codedData];
if ([unarchivedObject isKindOfClass:[NSArray class]]) {
// do something
} else if ([unarchivedObject isKindOfClass:[NSDictionary class]]) {
// do something else
}

Related

how to get objects from a json array in iphone?

I am working on an iPhone app which involves using json-framework.I am getting array using NSURL
[{"firstName":"X","lastName":"Y","id":1},{"firstName":"A","lastName":"B","id":2}]
How can i get these 2 objects as like if i query for id=1, the O/P is
id=1
firstName:X
lastName:Y
and putting it in a table.
I am googling the stuff from many days but didn't get any solution.
Please help me out , explanation through code is appreciated.
Thank You.
If your target SDK is ios4 or higher, you can use this project
https://github.com/stig/json-framework/
Once you add the source to your project, just
#import "SBJson.h"
and convert your Json string as follows
jsonResponse = [string JSONValue];
The method will fail if you don't have the full Json array in your string, but you can keep appending strings until it doesn't fail
To follow up for codejunkie's request below
you can assume in your data structure that the jsonResponse is an NSArray
In other implementations take care to test the response for NSArray or NSDictionary
NSArray * myPeople = [string JSONValue];
NSMutableDictionary * organizedData = [[NSMutableDictionary alloc] init];
for (NSDictionary * p in myPeople) {
[organizedData setValue:p forKey:[p valueForKey:#"id"]];
}
// now you can query for an id like so
// [organizedData valueForKey:#"1"]; and your output will be what you wanted from the original question
// just don't forget to release organizedData when you are done with it
https://github.com/johnezang/JSONKit
I use this to get data from a webservice that spits out 50 records each having another 20 internal elements similar to the one you specify...
I use the JSONKit in the following manner..(Had a look at SBJson a lot of user but i got confused from the word go.)
JSONDecoder *jArray = [[JSONDecoder alloc]init];
NSMutableArray *theObject = [[NSMutableArray alloc] init];
theObject = [jArray objectWithData:theResponseData];//objectWithString:theResponseString
NSMutableArray *csArray = [[NSMutableArray array] retain] ;
for(id key in theObject)
{
if([key valueForKey:#"firstName"] != Nil)
{
........
}
if([key valueForKey:#"lastName"] != Nil)
{
........
}
}
check it out and let me know if it works or not.. By the way Great responses guys... Good

having an NSDictionary that is always stored in an app

I would like to have a "permanent" NSDictionary in my app, in which from the beginning of the app launch, I can get access to the elements and even after I kill the app and start it again.
This NSDictionary needs to be stored tightly with the app. One way would be to just to create the NSDictionary from a Web Service data every time the app launches, then create a singleton class that would represent this NSDictionary, but I don't think this is good.
The NSDictionary will approximately hold 10-20 objects, where the key would be a NSString or NSDate and the value would be a NSArray. The NSArray would have a maximum of approximately 50 entries in it (on average probably there will only be 5-25 entries).
I am planning to use this NSDictionary as a part of a calculation that I am doing inside the delegate locationManager:didUpdateToLocation:fromLocation: each time a user moves every 50-100 meters.
Suggestions are appreciated on the best way I could do this.
Just having the NSDictionary in a singleton will not keep it between app launches, you'll need to save it to disk, and then read it from the disk when the app starts.
If you have no custom objects (subclasses you've created) in the NSDictionary, or in the NSArrays or non at all, you can use this method to save the NSDictionary is:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
and to open the dictionary from disk:
- (id)initWithContentsOfFile:(NSString *)path
However if you do have custom objects they will need to conform to the NSCoding protocol. And you'll have to use 2 different methods to save and open it:
NSCoding is a protocol, so in your header you need to add it on the end of the interface line:
#interface myClassName : NSObject <NSCoding> {
(where the only thing you should add is <NSCoding>)
Then in your implementation of your subclass, you need to add the following methods:
- (id)initWithCoder:(NSCoder *)decoder
and:
- (void)encodeWithCoder:(NSCoder *)encoder
The initWithCoder: method gets called when you want to unarchive(/open) your NSDictionary (which somewhere contains this class)
encodeWithCoder: is what gets called when your NSDictionary is archived(/saved).
You don't call either of these yourself. You need to add code in them:
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder])) {
aProperty = [[decoder decodeObjectForKey:#"aProperty"] retain];
anotherProperty = [[decoder decodeObjectForKey:#"anotherProperty"] retain];
aFloat = [decoder decodeFloatForKey:#"aFloat"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:aProperty forKey:#"aProperty"];
[encoder encodeObject:anotherProperty forKey:#"anotherProperty"];
[encoder encodeFloat:aFloat forKey:#"aFloat"];
}
You need to have similar lines for each value you want to store (generally all the properties, [and instance variables] your class has). Note the how the float line is different to the others.
The keys can be any string you want, as long each property has it's own unique key and that they match between the two methods. I personally use the name of the property as it's just easier to understand.
when you actually want to save your NSDictionary you use:
[NSKeyedArchiver archiveRootObject:myDictionary toFile:pathToMyDictionary];
and to open the dictionary:
NSDictionary *myDictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:pathToMyDictionary];
(depending on your code you may need to retain myDictionary)
To get the path to your dictionary (for both saving and opening) do:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pathToMyDictionary = [documentsDirectory stringByAppendingPathComponent:#"myDictionary.dat"];
Hope that helps, if you have any more questions about this answer, just comment :)
It sounds like you want NSUserDefaults. That stores app preferences but can be used other bits of data as well. It is essentially a dedicated, singleton dictionary that is universally accessible and is automatically saved.
You can easily generate an NSDictionary from a plist file using [NSDictionary dictionaryWithContentsOfFile:foo]
If appropriate, the plist could be included in your app bundle, or you could grab the data from a web service on first launch and write it out to a plist that the dictionary is loaded from in the future.

How do I archive an NSArray of NSDictionary (with NSCoding)?

Suppose I am holding data in an array like this
wordList = [[NSMutableArray alloc] init];
while ([rs next]) //Some database return loop
{
wordDict = [[NSMutableDictionary alloc] init];
[wordDict setObject:[NSNumber numberWithInt:[rs intForColumn:#"id"]] forKey:#"id"];
[wordDict setObject:[rs stringForColumn:#"word"] forKey:#"word"];
[wordList addObject: wordDict];
[wordDict release];
wordDict = nil;
}
But I want to store this result (i.e. wordList) in SQLite for later use - I guess using NSCoding. How would I do that?
(Feel free to point out any errors in how stuff is being alloc'ed if there are problems there).
If you don’t insist on serialization using NSCoding, there’s a writeToFile:atomically: method both on NSArray and NSDictionary. This will serialize your object into a property list (*.plist). The only catch is that all the objects in the “tree” to be serialized must be NSString, NSData, NSArray, or NSDictionary (see the documentation). I’m not sure how NSNumber fits in, but with a bit of luck it will be serialized and deserialized too. The inverse method that will turn the file back into a dictionary or an array is called initWithContentsOfFile:.
As for your code, I would just use the [NSMutableDictionary dictionary] convenience method that gets you an autoreleased dictionary. It’s shorter than the usual alloc & init and you save one line for the explicit release.

iPhone - User Defaults and UIImages

I've been developing an iPhone app for the last few months. Recently I wanted to up performance and cache a few of the images that are used in the UI. The images are downloaded randomly from the web by the user so I can't add specific images to the project. I'm also already using NSUserDefaults to save other info within the app.
So now I'm attempting to save a dictionary of UIImages to my NSUserDefaults object and get...
-[UIImage encodeWithCoder:]: unrecognized selector sent to instance
I then decided to subclass UIImage with a class named UISaveableImage and implement NSCoding. So now I'm at...
#implementation UISaveableImage
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:super forKey:#"image"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
if (self=[super init]){
super = [decoder decodeObjectForKey:#"image"];
}
return self;
}
#end
which isn't any better than where I started. If I was able to convert an UIImage to NSData I would be good, but all I can find are function like UIImagePNGRepresentation which require me to know what type of image this was. Something that UIImage doesn't allow me to do. Thoughts? I feel like I might have wandered down the wrong path...
You don't want to store images in NSUserDefaults. They're big blobs of data, and NSUserDefaults is stored as a plist; you want to write small bits of info to it.
You should write the images to disk, and then store the filenames to defaults:
NSString *filename = myImageFilename;
[UIImagePNGRepresentation(image) writeToFile: myImageFilename atomically];
[[NSUserDefaults standardDefaults] setObject: myImageFilename forKey: #"lastImageFilename"];
Stumbling upon this a year later. I would add (in case someone else stumbles here as well) that you should store the images in the cache directory and avoid iTunes trying to back them up.
- (NSString *)pathForSearchPath:(NSSearchPathDirectory)searchPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
NSString *directoryPath = [paths objectAtIndex:0];
return directoryPath;
}
- (NSString *)cacheDirectoryPath {
return [self pathForSearchPath:NSCachesDirectory];
}

Crash-proof NSDictionary

I'm looking to create a "crash-proof" NSDictionary as I'm using a JSON serializer that converts a server response into an NSDictionary. As as result, sometimes the key for the dictionary is not present. Currently, this will crash the application, however I'd rather an empty NSString was returned so I can display this in the interface.
A solution could be that I check for the key every time I access the dictionary, e.g.
if([returnedDictionary objectForKey:#"key"]){
// Display [returnedDictionary objectForKey:#"key"];
}else{
// Display #"";
}
However this soon results in bloated, hard-to-read code.
I had thought about creating a custom NSDictionary object, something like:
#interface NSSafeDictionary : NSDictionary .....
that overrides objectForKey with the above statement.
Is this a satisfactory approach?
Thanks
Are you always going to want to get strings out of your dictionary or will other objects be stored in it as well? If it's only strings, I think the easiest way around this is to construct a category on NSDictionary.
#interface NSDictionary ( EmptyStrings )
- (NSString *)stringForKey:(id)aKey;
#end
#implementation NSDictionary ( EmptyStrings )
- (NSString *)stringForKey:(id)aKey {
id object = [self objectForKey:aKey];
if (object == nil ) {
return #"";
}
if ([object isKindOfClass:[NSString class]) {
return object;
} else {
return nil;
}
}
#end
Given that it comes in over the network, I would think that you would want to sanitise the data more than just checking for empty values but if not, you don't really need to inherit from NSDictionary.
A simple utility method in your class would do the trick.
Or you could create a category on NSDictionary:
#interface NSDictionary (Safe)
-(NSString*)safeStringForKey:(NSString*)key;
#end
(I'm sure you can figure out the implementation.)