I am loading a plist via NSURLConnection into NSMutableData.
After that is done I want to read the PLIST into a NSMutableDictionary.
And then add the objects into my array to display them in a tableview.
But at the moment I don't know how to extract the data from NSMutableData into my NSMutableDictionary.
If I save the data local as plist on the iPhone in some folder and then read the plist into my Dictionary it works. But isn't there a way to do this directly?
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
receivedData = [[NSMutableData alloc] initWithData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSData *data = [[NSMutableData alloc] initWithData:receivedData];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSDictionary *myDictionary = [unarchiver decodeObjectForKey:#"Beverage"];
[unarchiver finishDecoding];
beverageArray = [[NSMutableArray alloc] init];
beverageArray = [myDictionary objectForKey:#"Beverage"];
NSLog(#"%#", beverageArray);
}
Before using NSURLConnection I used this which works:
- (void) makeDataBeverage {
beverageArray = [[NSMutableArray alloc] init];
NSMutableDictionary *beverageDic = [[NSMutableDictionary alloc]initWithContentsOfURL:[NSURL URLWithString:NSLocalizedString(#"beverage", nil)]];
beverageArray = [beverageDic objectForKey:#"Beverage"];
Now I want to the same with using NSURLConnection.
Assuming you have the complete data(*), you'll want to look into the NSPropertyListSerialization class. Its +propertyListWithData:options:format:error: method should get you what you're looking for, and you can use the options parameter to get the results as a mutable dictionary or array.
(*)It sounds like you have the complete data, since you say you can write it to a file and then read it in using dictionaryWithContentsOfFile: or similar, but it doesn't look like you're guaranteed to get it from the code you've shown. You're creating a new data in -connection:didReceiveData:, but that delegate method can be called multiple times as the data arrives in pieces. (I'm guessing it just happened to arrive all in one piece for your testing... this may not always be true, especially on a mobile device.) Instead, you probably want to create an empty mutable data when you start your NSURLConnection (or in -connection:didReceiveResponse:), append to it in -connection:didReceiveData:, and parse it in -connectiondidFinishLoading:. Or even better, since the property list parser can't do anything with a partial data anyway, use the new +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] if you're targeting iOS 5.0+.
Related
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
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.
In my app I have a table view where the user presses an "add" button in the nav bar and a new cell is added to the table view. The data from the table is loaded from an NSArray and each index in the array is storing an NSMutableDictionary with 4 Key-Value pairs. This array is saved to a .plist every time a cell is added. Now when the user selects a row in the table a detail view gets loaded. This detail view simply loads the data from the saved .plist depending on what row was selected.
In this detail view I want to allow the user to edit the data in the dictionary for that specific row. I've been trying different things and I can read the data from the dictionary and load it into the view but when I try and save the data back to the dictionary my app keeps terminating.
Can someone explain to me the proper way to reading and writing data to a an NSMutableDictionary?
This is how i'm writing the data back to the .plist:
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict = [array objectAtIndex:selectedRow];
NSString *tempA = [[NSString alloc] initWithFormat:#"%d pts", ivar_A];
NSString *tempB = [[NSString alloc] initWithFormat:#"%d pts", ivar_B];
NSString *tempC = [[NSString alloc] initWithFormat:#"%d pts", ivar_C];
[dict setValue:tempA forKey:#"keyA"];
[dict setValue:tempB forKey:#"keyB"];
[dict setValue:tempC forKey:#"keyC"];
[tempA release];
[tempB release];
[tempC release];
[array insertObject:dict atIndex:selectedRow];
//Save the modified array back to the plist
[array writeToFile:[self dataFilePath] atomically:YES];
[dict release];
[array release];
if you use [[NSMutableDictionary alloc] dictionaryFromContentsOfFile:#"pathtofile.plist], the contents of the dictionary will still be immutable, even if the top level container is mutable. It is specifically stated in Apple's reference.
NSDictionary Reference
The dictionary representation in the
file identified by path must contain
only property list objects (NSString,
NSData, NSDate, NSNumber, NSArray, or
NSDictionary objects). For more
details, see Property List Programming
Guide. The objects contained by this
dictionary are immutable, even if the
dictionary is mutable.
If you need to make the data structure writable again, you will need to make a copy of the loaded data structure using mutable types, like
[NSMutableDictionary dictionaryWithDictionary:oOrig]
etc.
I've got a thread (specifically an NSOperation) that runs to load some images for me for a scroll view when my main view asks for them. Any number of these NSOperations can be queued at once. So it goes with the filepath I give it and loads the image from the disk (as UIImages) and then sends the object back to my mainview by using performSelectorOnMainThread: and passing my mainview an NSDictionary of the object, and an image ID value. My main view is then supposed to insert the image object and the image ID string into an NSMutableDictionary that it has for the mainview to be able to use. I've verified that the NSMutableDictionary is allocated and initialized fine, but when the method the NSOperation calls tries to add the objects to the dictionary nothing happens. I've verified that the object and string i get from the dictionary the thread sent me are not null or anything but yet it doesn't work. Am I not doing something right or using a bad technique? What would anyone suggest to do in a situation like this where I need to add UIImages to an NSMutableDictionary from a thread? Thanks so much!
Here's the NSOperation code I use:
- (void)main {
NSString *filePath = [applicaitonAPI getFilePathForCachedImageWithID:imageID andSize:imageSize];
UIImage *returnImage = [[UIImage alloc] initWithContentsOfFile:filePath];
if (returnImage) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:2];
[dict setObject:returnImage forKey:#"IMAGE"];
[dict setValue:[NSString stringWithFormat:#"%d", imageID] forKey:#"IMAGE_ID"];
NSDictionary *returnDict = [[NSDictionary alloc] initWithDictionary:dict];
[dict release];
[mainViewController performSelectorOnMainThread:#selector(imageLoaderLoadedImage:) withObject:returnDict waitUntilDone:NO];
[returnDict release];
}
}
And here's the method on the main thread:
- (void)imageLoaderLoadedImage:(NSDictionary *)dict {
UIImage *loadedImage = [dict objectForKey:#"IMAGE"];
NSString *loadedImage = [dict valueForKey:#"IMAGE_ID"];
[imagesInMemoryDictionary setObject:loadedImage forKey:loadedImageID];
[self drawItemsToScrollView];
}
[mainViewController performSelectorOnMainThread:#selector(imageLoaderLoadedImage:) withObject:nil waitUntilDone:NO];
You're not passing returnDict as the parameter to the method. You're passing nil.
A couple of other thoughts:
you don't need to create returnDict. You can just use dict as the method parameter.
you're leaking returnImage.
edit
Since you apparently are passing returnDict as the parameter to the method, my other guess would be that mainViewController is nil. Other than that, your code looks functional.
I'm working on a homework app that uses custom Assignment objects for each assignment. I am trying to store an NSMutableArray (casted to an NSArray via initWithArray:) in standardUserDefaults but I'm having trouble with saving and reloading the array.
I have a table view from which you can choose to add a new assignment (which loads NewAssignmentViewController). When you save the assignment, it is pushed back to an array in AssigmentsViewController. And then you call it every time you load the UITableView which shows the assignments.
Here is the relating code:
-(void)saveToUserDefaults:(NSArray*)myArray{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:myArray] forKey:#"Assignments"];
[standardUserDefaults synchronize];
}
}
-(void)retrieveFromUserDefaults{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"Assignments"];
if (dataRepresentingSavedArray != nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if ([oldSavedArray count] != 0) {
[assignments setArray:[[NSMutableArray alloc] initWithArray:oldSavedArray]];
}
else {
assignments = [[NSMutableArray alloc] initWithCapacity:100];
}
}
}
-(void)backButtonPressed {
[self saveToUserDefaults:[[NSArray alloc] initWithArray:assignments]];
[self.navigationController popViewControllerAnimated:YES];
}
Please help. It does not load the array but does not give any error. Any tips about UserDefault or KeyedArchiver in general would be greatly appreciated.
Couple of things here:
If I understand you correctly, you're trying store an array whose contents are the assignment objects.
If you want to serialize these objects for storage into NSUserDefaults, the Assignment objects themselves need to conform the NSCoding protocol by overriding these methods:
- (void)encodeWithCoder:(NSCoder *)encoder;
- (id)initWithCoder:(NSCoder *)decoder;
Since you didn't post the code for your Assignment objects, dunno if you did this properly or at all. If you have you should be able to encode the object. See the Archives and Serializations Programming Guide for more.
As for NSUserDefaults, by my read, you're basically trying to store your application's object model there. Not the best idea. NSUserDefaults is best suited for use with light-weight persistent data: basic preferences, strings, scraps of universal data.
What I would do is write out your archived data to a file and load it when your view loads.
Here's some code from Beginning iPhone Development on that subject:
Creating an archive from an object or objects that conforms to NSCoding is relatively easy. First, we create an instance of NSMutableData to hold the encoded data and then create an NSKeyedArchiver instance to archive objects into that NSMutableData instance:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
After creating both of those, we then use key-value coding to archive any objects we wish to include in the archive, like this:
[archiver encodeObject:myObject forKey:#”keyValueString”];
Once we’ve encoded all the objects we want to include, we just tell the archiver we’re done, write the NSMutableData instance to the file system, and do memory cleanup on our objects.
[archiver finishEncoding]; BOOL success = [data writeToFile:#”/path/to/archive” atomically:YES];
[archiver release];
[data release];
To reconstitute objects from the archive, we go through a similar process. We create an NSData instance from the archive file and create an NSKeyedUnarchiver to decode the data:
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
After that, we read our objects from the unarchiver using the same key that we used to archive the object:
self.object = [unarchiver decodeObjectForKey:#”keyValueString”];
You'd also need to get your application's documents directory to save and load the files.
It's a wildly useful book, full of drop in code snippets. The chapter on persistence might be helpful for you. You might be much happier using Core Data for this task, come to think of it.
I'm not sure if this will fix your problem, but you don't have to pull the array out of Defaults as NSData. Check the NSUserDefaults reference and you'll see that Arrays are valid default objects.