Saving a NSMutableArray of custom objects - ios

I am trying to save an NSMutableArray to the NSUserDeafults. I have implemented the NSCoding protocol methods as so
-(id)initWithCoder:(NSCoder*)aDecoder{
self = [super init];
if(self!= nil){
self.subject = [aDecoder decodeObjectForKey:#"subj"];
self.notes = [aDecoder decodeObjectForKey:#"notes"];
self.dateDue = [aDecoder decodeObjectForKey:#"date"];
self.done = [[aDecoder decodeObjectForKey:#"done"] boolValue];
self.importancy = [aDecoder decodeObjectForKey:#"imp"];
}
return self;
}
and
-(void)encodeWithCoder:(NSCoder*)enCoder{
[enCoder encodeObject:self.subject forKey:#"subj"];
[enCoder encodeObject:self.notes forKey:#"notes"];
[enCoder encodeObject:self.dateDue forKey:#"date"];
[enCoder encodeObject:self.importancy forKey:#"imp"];
[enCoder encodeObject:[NSNumber numberWithBool:self.done] forKey:#"done"];
}
i save it like this
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:hwArray] forKey:ARRAY_KEY];
and get it like this
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *hwData = [currentDefaults objectForKey:ARRAY_KEY];
if (hwData != nil)
{
NSLog(#"data is not nil");
NSArray *savedHWArray = [NSKeyedUnarchiver unarchiveObjectWithData:hwData];
if (savedHWArray != nil){
NSLog(#"initiating array");
hwArray = [[NSMutableArray alloc] initWithArray:savedHWArray];
NSLog(#"hw array count: %i",[hwArray count]);
}
else{
hwArray = [[NSMutableArray alloc] init];
}
}
It seems perfect according to this question, yet I can't seem to load it beacuse even though I've put objects into the array and loaded it the array is always nil at another run. Any idea on what's the problem?

Another option would be to write your objects out to a file instead of NSUserDefaults. Check out the answer to this question here for a full code example of how to do that.

Related

Share custom data with today extension by NSUserDefaults

this is my first post, so please be kind and constructive with me.
Im trying to share an array of custom objects with my Today Extension via NSuserDefauls.
I know that I can't put custom objects in NSUserdefaults so i coded them by NSKeyedArchiver - NSKeyedUnarchiver to transform them in NSData.
This is my custom class
#import "Counter.h"
#implementation Counter
#dynamic city;
#dynamic address;
#dynamic event;
#dynamic date;
#dynamic time;
#dynamic counter;
#dynamic latitude;
#dynamic longitude;
- (void)encodeWithCoder:(NSCoder *)encoder
{
//Encode properties, other class variables, etc
[encoder encodeObject:self.city forKey:#"city"];
[encoder encodeObject:self.address forKey:#"address"];
[encoder encodeObject:self.event forKey:#"event"];
[encoder encodeObject:self.date forKey:#"date"];
[encoder encodeObject:self.time forKey:#"time"];
[encoder encodeObject:self.counter forKey:#"counter"];
[encoder encodeObject:self.latitude forKey:#"latitude"];
[encoder encodeObject:self.longitude forKey:#"longitude"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if( self != nil )
{
self.city = [decoder decodeObjectForKey:#"city"];
self.address = [decoder decodeObjectForKey:#"address"];
self.event = [decoder decodeObjectForKey:#"event"];
self.date = [decoder decodeObjectForKey:#"date"];
self.time = [decoder decodeObjectForKey:#"time"];
self.counter = [decoder decodeObjectForKey:#"counter"];
self.latitude = [decoder decodeObjectForKey:#"latitude"];
self.longitude = [decoder decodeObjectForKey:#"longitude"] ;
}
return self;
}
#end
This is the coding function inside the app.
- (void)saveInUserDefault {
//Counter is my Custom class
//I'm Using MagicalRecord
NSArray *array = [Counter MR_findAllSortedBy:#"date" ascending:NO];
NSMutableArray *defaultMutableArray = [NSMutableArray arrayWithCapacity:array.count];
NSUserDefaults *defaultArray = [[NSUserDefaults alloc] initWithSuiteName:#"group.LorenzoLoRe.Everycount"];
for (Counter *cnt in array) {
NSData *dataToDefault = [NSKeyedArchiver archivedDataWithRootObject:cnt];
[defaultMutableArray addObject:dataToDefault];
}
[defaultArray setObject:defaultMutableArray forKey:#"counts"];
[defaultArray synchronize];
}
At this point looking at the log, data seems to be write in NSUserDefaults
And here i try to retrive the data in the Extension
-
(void)takeLastCounts {
NSUserDefaults *defaultArray = [[NSUserDefaults alloc] initWithSuiteName:#"group.company.product"];
[defaultArray synchronize];
NSArray *dummyArray = [defaultArray arrayForKey:#"counts"];;
NSMutableArray *arrayFromDefault= NULL;
Counter *cter = nil;
for (NSData *data in dummyArray) {
cter = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[arrayFromDefault addObject:cter];
}
self.cnt_1 = arrayFromDefault[0];
self.cnt_2 = arrayFromDefault[1];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterShortStyle];
self.eventLabel.text = self.cnt_1.event;
self.dateLabel.text = [formatter stringFromDate:self.cnt_1.date];
self.counterLabel.text = self.cnt_1.counter.stringValue;
self.timeLabel.text = self.cnt_1.time;
}
When i try to execute the Extension, theres no errors but just empty Arrays.
In particular NSArray *dummyArray = [defaultArray arrayForKey:#"counts"]; seems to be empty, so somewhere the data don't flows.
What I'm doing wrong??
Any kind of tips will be ver appreciate.

This saved data doesn't appear to be accessible until relaunch iOS

I have a custom NSCoding class which stores and retrieves itself when necessary. However, it doesn't feed data to my table view until giving one entry to the array of custom Person objects inside it and restarting the app, then giving another. The first one disappears, however.
After that, it appears to load okay.
Here is the implementation of the class
#import "DataStorage.h"
#implementation DataStorage
#synthesize arrayOfPeople = _arrayOfPeople;
+ (DataStorage *)sharedInstance
{
static DataStorage *state = nil;
if ( !state )
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"DataStorageBank"];
if (data)
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[DataStorage alloc] init];
}
}
return state;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
if ([decoder decodeObjectForKey:#"DataStoragePeopleArray"]) {
_arrayOfPeople = [[decoder decodeObjectForKey:#"DataStoragePeopleArray"] mutableCopy];
} else {
_arrayOfPeople = [[NSMutableArray alloc] init];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_arrayOfPeople forKey:#"DataStoragePeopleArray"];
}
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageBank"];
}
#end
I add objects to the _arrayOfPeople like so:
Person *person = [[Person alloc] initWithFirstName:firstName personSurname:surname personCompay:company personPosition:position personEmail:email personMobile:mobile personProduct:product];
[[DataStorage sharedInstance].arrayOfPeople addObject:person];
[[DataStorage sharedInstance] save];
And load them into the table view by this:
Person *personAtIndex = [[DataStorage sharedInstance].arrayOfPeople objectAtIndex:indexPath.row];
[_arrayOfPeople addObject:personAtIndex];
cell.textLabel.text = personAtIndex.firstName;
cell.detailTextLabel.text = personAtIndex.surname;
Loading them in to the table view is in the method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
It looks like you only initialize _arrayOfPeople in initWithCoder:. However, if your data does not exist in user defaults already, you use state = [[DataStorage alloc] init] to initialize your shared instance. This does not call initWithCoder: so _arrayOfPeople is nil until after you save and load again, when it is finally initialized as [[NSMutableArray alloc] init]. To fix this, move _arrayOfPeople = [[NSMutableArray alloc] init] out of initWithCoder: and into init. (You could alternatively move it into sharedInstance, but it makes more sense in init since it is not specific to configuring the shared instance.)
Unrelated, but also make sure you synchronize.
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageBank"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

Saving data with NSUserDetaults and load after phone restart wont work

I managed to save an NSMutableArray in NSUserDefaults by first converting it to NSData - I dont deal with a lot of data and just want the data to be there after I switch off & on my phone - but the data does not show up in my table where I would display it. I write the NSUserDefaults back to my array upon loading. Maybe one of you has a hint...? Below the button action where I write to NSUserDefaults and the method viewDidLoad where I write NSUserDefaults to my original array (toDoitems)
- (IBAction)unwindToList:(UIStoryboardSegue *)segue
{
XYZAddToDoItemViewController *source = [segue sourceViewController];
XYZToDoItem *item = source.toDoItem;
if (item !=nil) {
[self.toDoitems addObject:item];
NSString *error;
NSData *data = [NSPropertyListSerialization dataFromPropertyList:self.toDoitems format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"itemArray"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self.tableView reloadData];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.toDoitems = [[NSMutableArray alloc] init];
self.toDoitems = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"itemArray"]];
}
Heres one way to do this
Add encoder decoder functions to your XYZToDoItem class
Something like this if say you had 2 strings in this class string1 and string2 :
(i havent compiled this code but you get the idea)
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.string1 forKey:#"string1"];
[aCoder encodeObject:self.string2 forKey:#"string2"];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
self.string1 = [aDecoder decodeObjectForKey:#"string1"];
self.string2 = [aDecoder decodeObjectForKey:#"string2"];
}
return self;
}
Then when you are ready to save do the following
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.myDataArray];
[userDefaults setObject:data forKey:#"storageName"];
// To load the data from NSUserDefaults
NSData *myData = [[NSUserDefaults standardUserDefaults] objectForKey:#"storageName"];
NSArray *temp = (NSMutableArray*)[NSKeyedUnarchiver unarchiveObjectWithData:myData];
self.myDataArray = (NSMutableArray*)[temp mutableCopy];

Unable to decode object from NSUserDefaults

I'm trying to decode my object from NSUserdefaults.
If I debug the function initWithCoder, it's executed okay, but when I look at my object that was decoded, all the properties are nil.
My initWithCoder function:
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if ( self != nil )
{
//decode the properties
self.SGTIN = [decoder decodeObjectForKey:#"SGTIN"];
self.GTIN = [decoder decodeObjectForKey:#"GTIN"];
self.SerialNumber = [decoder decodeObjectForKey:#"SerialNumber"];
self.Product = [decoder decodeObjectForKey:#"Product"];
self.DateTimeScanned = [decoder decodeObjectForKey:#"DateTimeScanned"];
self.Loaded = [decoder decodeBoolForKey:#"Loaded"];
}
return self;
}
My code to decode the object:
NSDictionary *tempDictionary = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSArray *tempArray =[tempDictionary allKeys];
for (NSString *key in tempArray){
if ([key length] > 8) {
if ([[key substringToIndex:8] isEqualToString:#"INXITEM:"]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:key];
ScannedItem *SITemp = (ScannedItem *)[NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
}
}
}
Just found it...
It was because the property Loaded (bool) is a new property and the object that was saved in NSUserDefaults didn't contain that property.
Cleared my NSUserDefaults and everything is okay now...
You aren't calling the correct superclass method.
It should be
self = [super initWithCoder:decoder];
Try using - (void)encodeWithCoder:(NSCoder *)coder method in ScannedItem object class
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.SGTIN forKey:#"SGTIN"];
//encode all properties
}

Working with NSUserDefaults when saving own datatype

i had read this topic How to save My Data Type in NSUserDefault? and get from there this useful part of code:
MyObject *myObject = [[MyObject alloc] init];
NSData *myObjectData = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];
[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:#"kMyObjectData"];
for saving data and this for reading
NSData *getData = [[NSData alloc] initWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"kMyObjectData"]];
MyObject *getObject;
[getData getBytes:&getObject];
its works very good when i save data in one ViewController and read it in other.
but when i whant to use it in the same class:
- (IBAction)linkedInLog:(UIButton *)sender
{
NSUserDefaults *myDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:#"linkedinfo"];
NSData *getData = [[NSData alloc] initWithData:myDefaults];
LinkedContainer *getObject;
[getData getBytes:&getObject];
if (!myDefaults) {
mLogInView = [[linkedInLoginView alloc]initWithNibName:#"linkedInLogInView" bundle:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewDidFinish:)
name:#"loginViewDidFinish"
object:mLogInView];
[self.navigationController pushViewController:mLogInView animated:YES];
if ((FBSession.activeSession.isOpen)&&(mLinkedInIsLogegOn)) {
mMergeButton.hidden = NO;
}
}
else{
mLinkedInIsLogegOn= YES;
mLinkedInInfo.mConsumer = getObject.mConsumer;
mLinkedInInfo.mToken = getObject.mToken;
}
}
something going wrong. in #selector:loginViewDidFinish i am saving my data to NSUserDefaults:
-(void) loginViewDidFinish:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
mLinkedInInfo.mConsumer = mLogInView.consumer;
mLinkedInInfo.mToken = mLogInView.accessToken;
NSData *myObjectData = [NSData dataWithBytes:(void *)&mLinkedInInfo length:sizeof(mLinkedInInfo)];
NSUserDefaults *lSave = [NSUserDefaults standardUserDefaults];
[lSave setObject:myObjectData forKey:#"linkedinfo"];
[lSave synchronize];
if (mLinkedInInfo.mToken) {
mLinkedInIsLogegOn = YES;
}
}
the program always crashes when it comes to else part. If somebody knows what I am doing wrong please help me)
error message: Thread 1 : EXC_BAD_ACCESS(code=2,address 0x8) when compiling getObject.Consumer
In the vast majority of cases, this is not going to be a meaningful way to serialize your object into an NSData:
MyObject *myObject = [[MyObject alloc] init];
NSData *myObjectData = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];
[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:#"kMyObjectData"];
The canonical way to do this would be for MyObject to adopt the NSCoding protocol. Based on the code you posted here, an adoption of NSCoding might look like this:
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super init])
{
mConsumer = [coder decodeObjectForKey: #"consumer"];
mToken = [coder decodeObjectForKey: #"token"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:mConsumer forKey: #"consumer"];
[coder encodeObject:mToken forKey:#"token"];
}
Once you had done that work, you would convert MyObject to and from NSData like this:
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myObject];
MyObject* myObject = (MyObject*)[NSKeyedUnarchiver unarchiveObjectWithData: data];
The code you have here is totally going to smash the stack and crash (because this line [getData getBytes:&getObject]; will cause the NSData to write bytes to the address of getObject, which is locally declared on the stack. Hence stack smashing.) Starting from your code, a working implementation might look something like this:
- (IBAction)linkedInLog:(UIButton *)sender
{
NSData* dataFromDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:#"linkedinfo"];
LinkedContainer* getObject = (LinkedContainer*)[NSKeyedUnarchiver unarchiveObjectWithData: dataFromDefaults];
if (!dataFromDefaults) {
mLogInView = [[linkedInLoginView alloc]initWithNibName:#"linkedInLogInView" bundle:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewDidFinish:)
name:#"loginViewDidFinish"
object:mLogInView];
[self.navigationController pushViewController:mLogInView animated:YES];
if ((FBSession.activeSession.isOpen)&&(mLinkedInIsLogegOn)) {
mMergeButton.hidden = NO;
}
}
else{
mLinkedInIsLogegOn= YES;
mLinkedInInfo.mConsumer = getObject.mConsumer;
mLinkedInInfo.mToken = getObject.mToken;
}
}
-(void) loginViewDidFinish:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
mLinkedInInfo.mConsumer = mLogInView.consumer;
mLinkedInInfo.mToken = mLogInView.accessToken;
NSData* objectData = [NSKeyedArchiver archivedDataWithRootObject: mLinkedInInfo];
[[NSUserDefaults standardUserDefaults] setObject: objectData forKey: #"linkedinfo"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (mLinkedInInfo.mToken) {
mLinkedInIsLogegOn = YES;
}
}
I agree with ipmcc 's answer, another viable option would be to add methods to your object to convert it to an NSDictionary. You could add methods to -initWithDictionary as well and should make instantiation very easy. Pull from dictionary in NSUserDefaults to use, convert to dictionary to save.
Here is an example of those 2 methods with generic data:
- (id)initWithDictionary:(NSDictionary *)dict
{
self = [super init];
// This check serves to make sure that a non-NSDictionary object
// passed into the model class doesn't break the parsing.
if(self && [dict isKindOfClass:[NSDictionary class]]) {
NSObject *receivedFences = [dict objectForKey:#"fences"];
NSMutableArray *parsedFences = [NSMutableArray array];
if ([receivedFences isKindOfClass:[NSArray class]]) {
for (NSDictionary *item in (NSArray *)receivedFences) {
if ([item isKindOfClass:[NSDictionary class]]) {
[parsedFences addObject:[Fences modelObjectWithDictionary:item]];
}
}
}
}
// More checks for specific objects here
return self;
}
- (NSDictionary *)dictionaryRepresentation
{
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
NSMutableArray *tempArrayForFences = [NSMutableArray array];
for (NSObject *subArrayObject in self.fences) {
if([subArrayObject respondsToSelector:#selector(dictionaryRepresentation)]) {
// This class is a model object
[tempArrayForFences addObject:[subArrayObject performSelector:#selector(dictionaryRepresentation)]];
} else {
// Generic object
[tempArrayForFences addObject:subArrayObject];
}
}
[mutableDict setValue:[NSArray arrayWithArray:tempArrayForFences] forKey:#"fences"];
return [NSDictionary dictionaryWithDictionary:mutableDict];
}
This is basically boilerplate code that is generated by a program I use called JSON Accelerator. It will read a JSON string returned by an API and generate object code for you. Not really a new concept, but makes created classes for API's very easy. And this bit of code works great for creating dictionaries to be saved to NSUserDefaults. Hope this helps.

Resources