Overwrite a value saved in NSUserDefaults - iphone

I have this method to save a mutable array named myWallet that contains instances of the Class Card.
- (void)saveMyWallet
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:self.myWallet] forKey:#"myWalletArray"];
[defaults synchronize];
}
The Card Class that I have has three instance variables: name, pin, and points. So far, saving new instances of the Card in UserDefaults is ok. I would just like to know some suggestions on how can I overwrite the value of points because as I proceed in the computation of points, I want to update it.
Here is my Card Class
Card.h
#import <Foundation/Foundation.h>
#interface Card : NSObject <NSCoding>
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *pin;
#property (nonatomic, strong) NSNumber *points;
#property (nonatomic, strong) NSMutableArray *pointsToDeduct;
- (double) subtractPoints: (double) requiredPoints;
- (void) encodeWithCoder:(NSCoder *)coder;
- (id) initWithCoder: (NSCoder *)coder;
#end
Card.m
#import "Card.h"
#implementation Card
#synthesize name = _name;
#synthesize pin = _pin;
#synthesize points = _points;
#synthesize pointsToDeduct = _pointsToDeduct;
- (id)initWithCoder:(NSCoder *)coder
{
self = [[Card alloc] init];
if(self != nil) {
self.name = [coder decodeObjectForKey:#"name"];
self.pin = [coder decodeObjectForKey:#"pin"];
self.points = [coder decodeObjectForKey:#"points"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.name forKey:#"name"];
[coder encodeObject:self.pin forKey:#"pin"];
[coder encodeObject:self.points forKey:#"points"];
}
- (double) subtractPoints:(double) requiredPoints
{
double latestPoints;
latestPoints = ([self.points doubleValue] - requiredPoints);
return latestPoints;
}
#end
And lastly, here is the delegate method by which the new value of the points (named resultingPoints) should come from.
- (void)perksDetailsViewController:(PerksDetailsViewController *)sender didPassRequiredPoints:(NSNumber *)requiredPoints withCard:(Card *)selectedCard
{
double perksPoints = [requiredPoints doubleValue];
self.resultingPoints = [NSNumber numberWithDouble:[selectedCard subtractPoints:perksPoints] ];
NSLog(#"points remaining %#", self.resultingPoints);
}
Bombard me with suggestions :) Thanks in advance!

From what I see, you actually save your object as NSData, so the logical approach is to get it back from the user defaults, unarchive it, update the properties, archive it and save it back to the user defaults.

Retrive the data from NSUserDefaults into runtime,Delete previous object for key and write back updated value.

Related

NSCoding and ARC

I'd like to archive my custom objects. ClassA holds a dictionary whose values are instances of ClassB.
Everything looks good ClassA's initWithCoder, and _dictionary is declared strong, but after init returns and I call callMeAfterInit, the _dictionary is empty (not null, a valid empty dictionary).
This ought to be simple, but I haven't used archiving much, so maybe I'm missing something basic. What might cause the dictionary to be emptied after the init returns?
Here's the essential code:
#interface ClassA : NSObject <NSCoding>
#property (strong, nonatomic) NSMutableDictionary *dictionary;
#end
#implementation ClassA
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.dictionary forKey:#"dictionary"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_dictionary = [decoder decodeObjectForKey:#"dictionary"];
// breakpoint here and I can inspect a good dictionary, full of ClassB values
}
return self;
}
- (void)callMeAfterInit {
NSLog(#"%#", self.dictionary);
// Log output here shows an empty dictionary (valid, but with 0 pairs)
}
#end
#interface ClassB : NSObject <NSCoding>
#property (strong, nonatomic) NSNumber *number;
#property (strong, nonatomic) NSArray *array;
#end
#implementation ClassB
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.number forKey:#"number"];
[encoder encodeObject:self.array forKey:#"array"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_number = [decoder decodeObjectForKey:#"number"];
_array = [decoder decodeObjectForKey:#"array"];
}
return self;
}
#end
You don't show the code that calls callMeAfterInit, so this is a guess.
More likely than not, you aren't talking to the same instance of ClassA. Did you happen to call [[ClassA alloc] init]; somewhere?

Archiving and retrieving array from a class in Objective-c [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
Hi i am trying to save data in a file with the help of a class that is saved to disk. It is saving the data but not retrieving correct data. Plz help me
Here is my ViewController.h
#interface MIGViewController : UIViewController
- (IBAction)hideKeyboard:(id)sender;
#property (strong, nonatomic) NSMutableArray * myArray;
#property (strong, nonatomic) NSString * pathToFile;
#property (weak, nonatomic) IBOutlet UITextField *nameField;
#property (weak, nonatomic) IBOutlet UITextField *idField;
#property (weak, nonatomic) IBOutlet UITextField *addressField;
#property (weak, nonatomic) IBOutlet UITextField *phoneField;
- (IBAction)addButtonTapped:(id)sender;
- (IBAction)saveButtonTapped:(id)sender;
#end
Here is view Controller.m
#import "MIGViewController.h"
#import "MIGStudent.h"
#interface MIGViewController ()
#end
#implementation MIGViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsPath = [paths objectAtIndex:0];
self.pathToFile = [documentsPath stringByAppendingPathComponent:#"students.sukh"];
NSFileManager * manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:self.pathToFile])
{
//File exists
self.myArray = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pathToFile];
UIAlertView * alert = [[UIAlertView alloc]initWithTitle:#"Students array" message:[self.myArray description]delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[alert show];
}
else
{
//File doesn't exist
self.myArray = [NSMutableArray array];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)addButtonTapped:(id)sender {
NSString * name =self.nameField.text;
NSString * address = self.addressField.text;
int studentID = [self.idField.text intValue];
int phoneNumber = [self.phoneField.text intValue];
MIGStudent * student = [[MIGStudent alloc] initWithName:name address:address studentID:studentID phoneNumber:phoneNumber];
[self.myArray addObject:student];
self.nameField.text = #"";
self.addressField.text = #"";
self.idField.text = #"";
self.phoneField.text = #"";
}
- (IBAction)saveButtonTapped:(id)sender {
[NSKeyedArchiver archiveRootObject:self.myArray toFile:self.pathToFile];
}
- (IBAction)hideKeyboard:(id)sender {
[self resignFirstResponder];
}
#end
Here is my class Student.h
#interface MIGStudent : NSObject <NSCoding>
#property (strong, nonatomic) NSString * name;
#property (strong, nonatomic) NSString * address;
#property (nonatomic) int studentID;
#property (nonatomic) int phoneNumber;
-(id)initWithName:(NSString *) name address:(NSString *)address studentID: (int)studentID phoneNumber:(int) phoneNumber;
#end
And here is Student.m
#import "MIGStudent.h"
#implementation MIGStudent
-(id) initWithName:(NSString *)name address:(NSString *)address studentID:(int)studentID phoneNumber:(int)phoneNumber
{
if (self=[super init])
{
self.name = name;
self.address = address;
self.phoneNumber = phoneNumber;
self.studentID = studentID;
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder
{
//Used when saving to disk
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeObject:self.address forKey:#"address"];
[aCoder encodeInt:self.phoneNumber forKey:#"phoneNumber"];
[aCoder encodeInt:self.studentID forKey:#"studentID"];
}
-(id) initWithCoder:(NSCoder *)aDecoder
{
//Used when reading from disk
self.name = [aDecoder decodeObjectForKey:#"name"];
self.address = [aDecoder decodeObjectForKey:#"address"];
self.phoneNumber = [aDecoder decodeIntForKey:#"phoneNumber"];
self.studentID = [aDecoder decodeIntForKey:#"studentID"];
return self;
}
#end
And here is what i get output every time
I think everythings is working properly but there is some problem while retrieving the data
Thanks in advance
That's not a problem with your implementation of NSCoding, that's actually not even a problem at all! That's the way an NSArray prints itself literally (the result of the -description method). The fact that it does this indicates that the archiving/de-archiving process went smoothly. The array is the set of parenthesis (), and the objects within are in the format <Class : memory address>. If you had an array of invalid objects, then your array would most likely refuse to print, or crash when the students objects were added to it.
You can take advantage of this in your MIGStudent class and override -description to print a friendlier format. For example:
-(NSString*)description {
return [NSString stringWithFormat:#"<%# : %p Student named: %# - who lives at: %# With the ID number: %d and the phone number: %d>", NSStringFromClass(self.class), self, self.name, self.address, self.studentID, self.phoneNumber];
}
As a sidenote, initializers always call through to super. Your initWithCoder: method will always return nil.
Your initWithCoder: method is incorrect it will fail as currently it will return nil or some garbage value, it should be:
-(id) initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
//Used when reading from disk
self.name = [aDecoder decodeObjectForKey:#"name"];
self.address = [aDecoder decodeObjectForKey:#"address"];
self.phoneNumber = [aDecoder decodeIntForKey:#"phoneNumber"];
self.studentID = [aDecoder decodeIntForKey:#"studentID"];
}
return self;
}
Secondly you can either override description or debugDescription method of your class to return custom information about that class.

initWithCoder for loading xib example [duplicate]

This question already has answers here:
Objective C - How do I use initWithCoder method?
(2 answers)
Closed 9 years ago.
I was reading about initializing the archived objects from a XIB file and found that
- (id)initWithCoder:(NSCoder *)aDecoder
is a way of doing it. But I am not able to get a hang around this. Can someone show me an simple example of how to do this?
Thanks a ton
The NSCoder class is used to archive/unarchive (marshal/unmarshal, serialize/deserialize) of objects.
This is a method to write objects on streams (like files, sockets) and being able to retrieve them later or in a different place.
I would suggest you to read Archiving
You also need to define the following method as follows:
- (void)encodeWithCoder:(NSCoder *)enCoder
{
[super encodeWithCoder:enCoder];
[enCoder encodeObject:instanceVariable forKey:INSTANCEVARIABLE_KEY];
// Similarly for the other instance variables.
....
}
And in the initWithCoder method initialize as follows:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder]) {
self.instanceVariable = [aDecoder decodeObjectForKey:INSTANCEVARIABLE_KEY];
// similarly for other instance variables
....
}
return self;
}
You can initialize the object standard way i.e
CustomObject *customObject = [[CustomObject alloc] init];
Example taken from this answer
You can use it in following way:
.h file
#interface Score : NSObject {
NSString *Username;
NSString *TotalPoints;
NSString *LifeRemains;
NSString *ScoreDate;
}
#property (nonatomic, retain) NSString *Username;
#property (nonatomic, retain) NSString *TotalPoints;
#property (nonatomic, retain) NSString *LifeRemains;
#property (nonatomic, retain) NSString *ScoreDate;
in .m file
#synthesize Username, TotalPoints, LifeRemains, ScoreDate;
- (void)encodeWithCoder:(NSCoder *)encoder
{
//Encode properties, other class variables, etc
[encoder encodeObject:self.Username forKey:kScoreUsername];
[encoder encodeObject:self.TotalPoints forKey:kScoreTotalPoints];
[encoder encodeObject:self.LifeRemains forKey:kScoreLifeRemains];
[encoder encodeObject:self.ScoreDate forKey:kScoreDate];
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if( self != nil )
{
//decode properties, other class vars
self.Username = [decoder decodeObjectForKey:kScoreUsername];
self.TotalPoints = [decoder decodeObjectForKey:kScoreTotalPoints];
self.LifeRemains = [decoder decodeObjectForKey:kScoreLifeRemains];
self.ScoreDate = [decoder decodeObjectForKey:kScoreDate];
}
return self;
}
Happy Coding...

Converting a NSObject into NSData

I am having an issue in converting a NSObject into NSData. I have a class which inherits NSObject.
When i tried to convert the object of that particular class into NSData as follows :
NSData *dataOnObject = [NSKeyedArchiver archivedDataWithRootObject:classObject];
but it gives out exception stating that -[classObject encodeWithCoder:]: unrecognized selector sent to instance ..
I have also added the object to a newly created array as
NSMutableArray *wrapperedData = [NSMutableArray arrayWithObject: classObject];
NSData *dataOnObject = [NSKeyedArchiver archivedDataWithRootObject:value];
But still , its giving out exception.
So I need to extract the bytes from the object classObject.
Any help would be greatly appreciated ...
awaiting for your reply ...
You must implement for your own object such as:
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeInt:self.age forKey:#"age"];
[aCoder encodeObject:self.email forKey:#"email"];
[aCoder encodeObject:self.password forKey:#"password"];
}
BOOL success = [NSKeyedArchiver archiveRootObject:person toFile:archiveFilePath];
and:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.age = [aDecoder decodeIntForKey:#"age"];
self.email = [aDecoder decodeObjectForKey:#"email"];
self.password = [aDecoder decodeObjectForKey:#"password"];
}
return self;
}
Person *unarchivePerson = [NSKeyedUnarchiver unarchiveObjectWithFile:archiveFilePath];
You need to implement encodeWithCoder: on your custom class, serializing all of its attributes using the NSCoder passed into it. If its attributes include any more custom classes, they'll need encodeWithCoder: implementing too.
Instead of
NSData *dataOnObject = [NSKeyedArchiver archivedDataWithRootObject:classObject];
it should be
NSData *dataOnObject = [[NSUserDefaults standardUserDefaults] objectForKey:#"someKey"];
But that's just for reading data in that's already been saved. If you want to save an object as NSData then you have this:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:classObject] forKey:#"someKey"];
But that's not all. Your classObject has to implement the NSCoding protocol and have the two methods encodeWithCoder: and initWithCoder: since it's not an NS object in order for it to work.
you can only archive objects that support the NSCoding protocol
You can convert any object to NSData with the NSCoding protocol.
You can find sample code to do this here:
http://samsoff.es/posts/archiving-objective-c-objects-with-nscoding
This is a example of custom object converted to NSData (so it can be then saved into user defaults)
Create the following files:
Catalog.h
#interface Catalog : NSObject
#property (nonatomic, assign) int pk;
#property (nonatomic, copy) NSString *catalogName;
#property (nonatomic, copy) NSString *catalogDescription;
#property (nonatomic, assign) int catalogEdition;
#property (nonatomic, assign) int catalogTotalPages;
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
#end
Catalog.m
#import <Foundation/Foundation.h>
#import "Catalog.h"
#implementation Catalog
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.catalogName forKey:#"catalogName"];
[aCoder encodeObject:self.catalogDescription forKey:#"catalogDescription"];
[aCoder encodeInt:self.catalogEdition forKey:#"catalogEdition"];
[aCoder encodeInt:self.catalogTotalPages forKey:#"catalogTotalPages"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.catalogName = [aDecoder decodeObjectForKey:#"catalogName"];
self.catalogDescription = [aDecoder decodeObjectForKey:#"catalogDescription"];
self.catalogEdition = [aDecoder decodeIntForKey:#"catalogEdition"];
self.catalogTotalPages = [aDecoder decodeIntForKey:#"catalogTotalPages"];
}
return self;
}
#end
Finally in your controller include header files
#import "Catalog.h"
And add this code to use your object (in this case im saving into user defaults)
Catalog *catalog = [[Catalog alloc] init];
catalog.catalogName = #"catalogName";
catalog.catalogDescription = #"catalogName";
catalog.catalogEdition = 1;
NOTE: in this line of code is where the actual data passing is taking place
//archiving object to nsdata
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:catalog];
[[NSUserDefaults standardUserDefaults] setObject:encodedObject forKey:#"keyName"];
[[NSUserDefaults standardUserDefaults] synchronize];
In case you want to get your object back from NSData
NSData *nsData = [[NSUserDefaults standardUserDefaults] objectForKey:#"keyName"];
//unarchiving object to nsdata
Catalog *selectedCatalog = [NSKeyedUnarchiver unarchiveObjectWithData: nsData];
Hope this helps!

Sorting an NSMutableArray with custom objects 'overwrites' some objects

For a little iPhone application I am making, I want to sort a NSMutableArray.
I found 2 ways of doing this, but they both result in the same thing. Sorting the array will cause some objects to 'overwrite' eachother.
First off, here is my code:
AppDelegate.h
NSMutableArray* highScores;
Somewhere down that AppDelegate.h, I also make this variable a property so that I can access it from differen classes:
#property (retain, nonatomic) NSMutableArray* highScores;
When my application starts, I read the high scores from a file and import them into my NSMutableArray.
AppDelegate.m
NSMutableData* data = [NSData dataWithContentsOfFile:highScoresPath];
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.highScores = [decoder decodeObjectForKey:#"highscoresArray"];
The objects I store in this NSMutableArray are from the type HighScore.
HighScore.h
#interface HighScore : NSObject {
int score;
int roundsPlayed;
int wrongAnswers;
NSString* name;
NSDate* datetime;
}
#property int score;
#property int roundsPlayed;
#property int wrongAnswers;
#property (nonatomic, copy) NSDate* datetime;
#property (nonatomic, copy) NSString* name;
- (id) init;
- (void) update:(int)roundScore:(BOOL) correct;
#end
HighScore.m
#import "HighScore.h"
#implementation HighScore
#synthesize score, roundsPlayed, wrongAnswers, name, datetime;
- (id) init
{
self.name = #"";
self.score = 0;
self.roundsPlayed = 0;
self.wrongAnswers = 0;
self.datetime = [NSDate date];
return self;
}
- (void) update:(int)roundScore:(BOOL) correct
{
self.score += roundScore;
if (!correct)
self.wrongAnswers++;
self.roundsPlayed++;
self.datetime = [NSDate date];
}
- (id) initWithCoder:(NSCoder *) decoder
{
self.name = [[decoder decodeObjectForKey:#"name"] retain];
self.score = [decoder decodeIntForKey:#"score"];
self.roundsPlayed = [decoder decodeIntForKey:#"roundsPlayed"];
self.wrongAnswers = [decoder decodeIntForKey:#"wrongAnswers"];
self.datetime = [[decoder decodeObjectForKey:#"datetime"] retain];
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeInt:self.score forKey:#"score"];
[encoder encodeInt:self.roundsPlayed forKey:#"roundsPlayed"];
[encoder encodeInt:self.wrongAnswers forKey:#"wrongAnswers"];
[encoder encodeObject:self.datetime forKey:#"datetime"];
}
- (NSComparisonResult) compareHighscore:(HighScore*) h
{
return [[NSNumber numberWithInt:self.score] compare:[NSNumber numberWithInt:h.score]];
}
#end
Now, when I try to sort my array by using the following code:
NSArray *sortedArray;
sortedArray = [highScores sortedArrayUsingSelector:#selector(compareHighscore:)];
It somehow screws up my highScores array, I get an X amound of highscores with the same score and name.
What am I doing wrong?
I'm noticing that in your initWithCoder: method, you're not doing this:
if (self = [super initWithCoder:coder]) {
// Decode your stuff here
}
Same with your regular init method. There needs to be a call to [super init].
Also, since you defined your string properties as copy and you're using the property syntax, there's no need to retain them. They will be retained for you by the synthesized accessor.
Otherwise, your code looks fine to me. Just remember: every init method must always have a call to a super's init... method.
You're trying to sort using #selector(compare:), not #selector(compareHighscore:), which I presume was your intention.
try
sortedArray = [highScores sortedArrayUsingSelector:#selector( compareHighscore: )];
Post the actual compareHighscore: method. The most important thing is that it has to be consistent, that is if a <= b and b <= c, then a <= c and if a < b and b < c then a < c. If you managed to write a compare method that is not consistent, anything can happen.