UserDefaults/KeyedArchiver Frustrations - iphone

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.

Related

How to access the string data outside the function and class

I am trying to access the nsstring data out side the function and also outside the class.
How can I access useridStr outside the function and class? This is my code:
-(id)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *loginStatus = [[NSString alloc] initWithBytes:[webData mutableBytes]
length:[webData length] encoding:NSUTF8StringEncoding];
NSDictionary *loginDict = [[loginStatus JSONValue] objectForKey:#"UserDetails"];
NSArray *userId = [loginDict valueForKey: #"userid"];
NSString *useridStr = [userId lastObject];
NSLog(#"--------------....%#", useridStr);
}
NSUserDefaults *pre = [NSUserDefaults standardUserDefaults];
[pre setObject:useridStr forKey:#"useridStr"];
where you want to need string:
NSUserDefaults *pre =[NSUserDefaults standardUserDefaults];
NSString * useridStr =[pre stringForKey:#"useridStr"];
OR
In .h file of anotherView
-(id)initUserInfo:(NSString *)string;
In .m file of anotherView
-(id)initUserInfo:(NSString *)string{
if (self = [super initWithNibName:#"nextView" bundle:nil]) {
useridStr=string
}
return self;
}
In .m File of firstView
-(IBAction)btnNext_TouchUpInside:(id)sender{
nextView *second =[[nextView alloc]initUserInfo:useridStr];
[self presentModalViewController:second animated:NO];
}
What Piyush suggested is also correct. But there is also another way to achieve it.
As he suggested NSUserDefaults to save data you should keep in mind that NSUserDefaults is generally used to save data like preferences which you want it to be stored even after Application is closed by user and you want those data again when you start your application.
So if you want to save data like preferences go for NSUserDefaults. If you want your data to be available throughout your application while its running and you do not need to save them like preferences I would recommend you declare that object globally in Appdelegate file and access them whenever you need. You should not store them as NSUserDefaults because as Apple document says whatever you store in NSUserDefaults it will be saved in user's default database. So that will consume memory of your device. So in short saving everything to NSUserDefaults won't be a good idea if we consider memory managent concepts.

NSMutableData Plist for NSMutableDictionary

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+.

Core Data Managed Object Context Saving Problem

I am trying to save an array into core data using NSData but my ManagedObjectContext says there are 0 objects and when I call it, I have it appearing as NULL. I have an entity called Event and 3 attributes in it (chatArray,...,...). I have tried for 11 hours and can't figure it out. I believe I am setting it wrong because the NSData is correct. How should I be setting this???
UPDATE
I am developing a chat application and I have the chat messages in a table view (It's an array of data). I need to save all the chat history when you exit the app and reload it. I have the messages coming in as strings and add it to the array for the table. If I didn't do an array, and I added the messages as strings of text to core data how would I add them to the array for the table view when you reload the app?
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSManagedObject *managedObject;
//
NSArray *array = [[[NSArray alloc]initWithObjects:#"one",#"two",#"three", nil]autorelease];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSLog(#"data %#",data);
NSLog(#"Array %#",[NSKeyedUnarchiver unarchiveObjectWithData:data]);
[(Event*)managedObject setValue:data forKey:#"chatArray"];
if ([self managedObject])
{
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext]];
[(Event *)managedObject setChatArray:data]; }
else {
Event *event = [[[Event alloc] initInsertingIntoManagedObjectContext:managedObjectContext]autorelease];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext]];
[event setChatArray:data];
}
NSError *error;
[managedObjectContext save:&error];
NSArray *myArray = [NSKeyedUnarchiver unarchiveObjectWithData:[(Event*)managedObject valueForKey:#"chatArray"]];
NSLog(#"chatArray %#",myArray);
Brandon,
First, in Core Data, BLOBs should be stored in leaf nodes (i.e. an entity that just contains the BLOB and a back to one relationship. (This pattern/convention has emerged because it is almost trivial to get a retain cycle of large blobs when there are other relations in the entity.)
Second, why are you storing these strings as an array and not as an entity with a time stamp, etc.?A BLOB is not particularly more efficient than individual rows plus the system can both search the messages and more flexibly store the rows. SQLite handles strings specially.
Third, it appears that you are composing your class rather than inheriting from your model entity, why? This makes your code more complex.
Finally, It is really hard to tell what you are trying to do. Could you include your full .h file? And the full method declaration?
Andrew

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.

NSKeyedUnarchiver memory leak issue

I have problem with this code, it's working on debug environment. On the instruments I'm seeing memory leak problem on this function, instruments is giving warning that
Category Event Type Timestamp Address Size Responsible Library Responsible Caller
27 SocialNetwork Malloc 00:19.951 0x3d64d20 80 Foundation -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:]
- (NSMutableArray *)GetDataInstanceToUserDefaults{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSData *storedObject = [userDefaults objectForKey:#"MyDataKey"];
NSMutableArray *data;
if(storedObject != nil)
{
NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData:storedObject];
if(savedArray != nil)
data = [[NSMutableArray alloc] initWithArray:savedArray];
else
data = [[NSMutableArray alloc] init];
}else{
data = [[NSMutableArray alloc] init];
}
return data;
}
I didn't understand where is the my problem ?
Thank you for your support
Edit : By the way I should give more detail about this problem,this function (as you can see) is storing my object. My object is custom class and storing in the NSMutableArray.
I already added these methods inside of the my custom class
-(void)encodeWithCoder:(NSCoder *)coder{
-(id)copyWithZone:(NSZone*)zone {
-(id)initWithCoder:(NSCoder *)coder{
I think the problem is most likely in the initWithCoder: method of your custom class. It is leaking but the analyzer reports it as being in the archiver.
Unrelated to your problem, I would caution you against using [[NSMutableArray alloc] init] to initialize collections, especially mutable collections. Instead use, [[NSMutableArray alloc] initWithCapacity:1]. I've seen strange problems using just init that were cleared up by using initWithCapacity.