I want to save an NSMutableArray to a file and load it every time my program runs. What is the best solution for this?
This is my example:
// I add a new item to ARRAYLIST
NSString *textNewCategory = [alert textFieldAtIndex:0].text;
[ARRAYLIST insertObject:textNewCategory atIndex:0];
// I save ARRAYLIST
userCategory = [NSUserDefaults standardUserDefaults];
[userCategory setObject:ARRAYLIST forKey:#"newCategory"];
NSLog(#"ARRAYLIST 1:%d", ARRAYLIST.count); // EX: In this case, it has count : 30
//In viewdidload, I get array that I saved before
userCategory = [NSUserDefaults standardUserDefaults];
arrayNewCategory = [[NSMutableArray alloc] initWithArray: [userCategory objectForKey:#"newCategory"]];
ARRAYLIST = arrayNewCategory;
NSLog(#"ARRAYLIST 2:%d", ARRAYLIST.count); // In this moment, it only has count: 29.
I don't understand why the re-loaded array has only 29 items instead of the 30 that I expect.
Save the array to a plist:
[array writeToFile:path atomically:NO]
and retrieve it:
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:path];
Related
I got problem with saving and comparing data. First I download data and save my data (array,dict)to userDefaults. After redownloading I need to compare if my new array have some new data which I haven't saved in userDefaults. So its mean I should find the data that is not the same inside my old data and add them to my new array.
NSMutableDictionary* tmpDict = [NSMutableDictionary new];
NSMutableDictionary* copyDict = [NSMutableDictionary new];
NSMutableArray *dataGroupsArr = [NSMutableArray new];
NSMutableDictionary *dataGroupsDict = [NSMutableDictionary new];
dataGroupsDict[#"name"] = #"FirstGroup"; // I dont need groups at the moment
NSMutableArray *datas = [[NSMutableArray alloc]init];
FOR ........ (parser)
{
NSMutableDictionary *data = [[NSMutableDictionary alloc]init];
data[#"dataID"] = [#"some data from parser"];
[datas addObject:data];
}];
dataGroupsDict[#"datas"] = datas;
[dataGroupsArr addObject:dataGroupsDict];
tmpDict[#"dataGroups"] = dataGroupsArr;
After I save data Im trying to load them from userdefualts
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
copyDict = [userDefaults objectForKey:#"dataDownloadArr"];
//data never added
if(copyDict == nil){
[userDefaults setObject:tmpDict forKey:#"dataDownloadArr"];
[userDefaults synchronize];
} else {
for (int i = 0; i< [copyDict[#"dataGroups"][0][#"datas"]count]; i++){
NSLog(#"%#", copyDict[#"dataGroups"][0][#"datas"][i][#"dataID"]);
}
}
Now I don't know how to compare data, and if there is new data in my new array how to add it to old one.
Did you try to use NSData instance method isEqualToData?
Two data objects are equal if they hold the same number of bytes, and
if the bytes at the same position in the objects are the same.
https://developer.apple.com/documentation/foundation/nsdata/1409330-isequaltodata
I was also facing same issue what I did is I asked the backend developer to send a value as modified_date and create a dictionary containing dataArray and modified_date. So after re-downloading you can just check for modified_date and replace the dataArray instead of comparing every array element.
i'm getting [__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object when I try to edit an object which I get from NSUserDefault.
I have searched through stackoverflow and tried making the mutable copy after retrieving the data but still getting the error. Here is the current code
I set this in the first view controller
NSMutableArray *settings=[[NSMutableArray alloc]init];
[settings addObject:[#{
#"single":#"Player1",
#"multiplesame":[[NSMutableArray alloc] initWithObjects:#"Player1",#"Player2",#"Player3",#"Player4",#"Player5", nil],
#"multipleDiff":[UIDevice currentDevice].name
}mutableCopy]];
[Reusables storeArrayToDefaults:SETTINGS_DETAILS objectToAdd:settings];
Now in another view controller, I'm trying to change the value of Player1 of single key to a name. For that this is how I retrieve the data.
NSMutableArray *setDetails = [[[NSUserDefaults standardUserDefaults] arrayForKey:SETTINGS_DETAILS] mutableCopy];
//setDetails= [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] arrayForKey:SETTINGS_DETAILS]] ;
//setDetails = [setDetails mutableCopy];
NSLog(#"Details:%#",setDetails);
The log prints fine
Details:(
{
multipleDiff = "iPhone Simulator";
multiplesame = (
Player1,
Player2,
Player3,
Player4,
Player5
);
single = Player1;
}
)
But now when I try to change the value like this it gets crashed with the above error
[[setDetails objectAtIndex:0] setObject:#"Any Name" forKey:#"single"] ;
For reference this is how I save in NSUserDefaults
+(void)storeArrayToDefaults :(NSString *)keyName objectToAdd:(NSMutableArray *)arrayData
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:arrayData forKey:keyName];
[defaults synchronize];
}
Try this
NSMutableArray *setDetails = [NSMutableArray arrayWithArray:[[[NSUserDefaults standardUserDefaults] arrayForKey:SETTINGS_DETAILS] mutableCopy]];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[setDetails objectAtIndex:0]];
[dict setObject:#"Any Name" forKey:#"single"];
[setDetails replaceObjectAtIndex:0 withObject:dict];
Everything in NSUserDefaults is immutable. You create a mutable copy of the outer container but its contents are still immutable. So, to edit them, you need to take a mutable copy, update that and then reinsert (and re-add to defaults).
[[setDetails objectAtIndex:0] setObject:#"Any Name" forKey:#"single"];
changes to
NSMutableDictionary *dict = [[setDetails objectAtIndex:0] mutableCopy];
[dict setObject:#"Any Name" forKey:#"single"];
[setDetails replaceObjectAtIndex:0 withObject:dict];
In my first function i create an NSMutableDictionary and saves it in an array.
NSMutableArray* tempPlayersArray = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] arrayForKey: #"kgpsTempArray"]];
NSMutableDictionary *tempPlayerDictArray = [[NSMutableDictionary alloc] init];
if (!(userDeviceName)) {
[tempPlayerDictArray setValue:userDeviceName forKey:#"DeviceName"];
}else{
[tempPlayerDictArray setValue:#"empty" forKey:#"DeviceName"];
}
[tempPlayersArray addObject:tempPlayerDictArray];
[defaults setObject:tempPlayersArray forKey:#"kgpsTempArray"];
[defaults synchronize];
In my second function i get it as NSCFDictionary - which is not mutable.
NSMutableArray* tempPlayersArray = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] arrayForKey: #"kgpsTempArray"]];
NSMutableDictionary *dictionaryForSearching = [[NSMutableDictionary alloc] init];
NSLog(#"%#",[dictionaryForSearching class]);
dictionaryForSearching = [tempPlayersArray objectAtIndex:index];
NSLog(#"%#",[[tempPlayersArray objectAtIndex:index] class]);
NSLog(#"%#",[dictionaryForSearching class]);
The first log shows "NSDictionaryM".
The second log shows "NSCFDictionary".
And the third shows "NSCFDictionary" as well...
Can anyone explain me why? And how to fix it?
NSUserDefaults works with inmutable objects, so that's the reason that when you return your dictionary it's changed.
You can try this:
dictionaryForSearching = [[NSMutableDictionary alloc] initWithDictionary:[tempPlayersArray objectAtIndex:index]];
Yes, NSUserDefaults is free to copy, persist, and deserialize as it likes. Assume it does not return mutable objects. If you need a mutable object, make a mutable copy.
Every thing depends from here:
NSMutableArray* tempPlayersArray = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] arrayForKey: #"kgpsTempArray"]];
Reading NSUserDefaults always give you Immutable object.
Thanks for you time and reading this. What I'm trying to do is figure out why this NSLog is telling me the NSArray is always null, no matter what. I'm thinking that the problem is that I'm initiating the NSMutableArray wrong. Could you perhaps take a look and decide whether or not I did it right, and if at all possible give me a way to pass the array into the NSMutableArray?
Thanks!
//Get Defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *favoriteArray = [[defaults objectForKey:#"favorites"] copy];
//Declares Mutable Array
self.favorites = [[NSMutableArray alloc] initWithObjects:favoriteArray, nil];
NSLog(#"array: %#", favorites);
UPDATE: I figured it out. It turns out you have to declare it with initWithArray rather than trying to add it as an object
Solution:
- (void)viewDidLoad {
//Get Defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *favoriteArray = [[defaults objectForKey:#"favorites"] copy];
//Declares Mutable Array
self.favorites = [[NSMutableArray alloc] initWithArray:favoriteArray];
[super viewDidLoad];
}
The way to do this is using the arrayWithArray and here is how you do it:
myNSMutableArray = [NSMutableArray arrayWithArray:myArray];
Do you ever set an object in your user defaults for the "favorites" key?
I want to store an array of UIImage and I do this:
//in didFinishLaunchingWithOption
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"theKey"];
if (data == NULL) arrayImage = [[NSMutableArray alloc] init];
else {arrayImage = [[NSMutableArray alloc] init]; arrayImage = [NSKeyedUnarchiver unarchiveObjectWithData:data];}
NSLog(#"arrayImage:%#", arrayImage);
//and in applicationDidEnterBackground
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arrayImage];
[defaults setObject:data forKey:#"theKey"];
NSLog(#"arrayImage:%#", arrayImage);
when app run in didFinishLaunchingWithOption in nslog I see all object in my array, but when I use it, I have a crash that say "[__NSArrayM count]: message sent to deallocated instance" why?
I'm not completely sure, but I think that
+(id)unarchiveObjectWithData:(NSData *)data
gives you an autoreleased object, so you may retain it. And I think it will give you a non-mutable object, so when you will try to add or remove objects from it you will get an error (I'm no sure about this, but I think I once was in this situation...)
I would rewrite some part of your code:
...
if (data == nil)
{
arrayImage = [[NSMutableArray alloc] init];
} else
{
//arrayImage = [[NSMutableArray alloc] init]; //why to allocate and initialize if you are goind to unarchive it?
//arrayImage = [[NSKeyedUnarchiver unarchiveObjectWithData:data] retain]; //note the retain here
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data]; //the unarchive won't mantain mutability (I guess).
arrayImage = [NSMutableArray arrayWithArray:arr]; //create a mutable copy
}
...
I assume you're not using ARC. The problem is in:
if (data == NULL)
arrayImage = [[NSMutableArray alloc] init];
else {
arrayImage = [[NSMutableArray alloc] init];
// HERE
arrayImage = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
At HERE, you replace the value in arrayImage with a new instance for the keyed unarchiver. The value you init'd just previously is lost (in fact, leaked). The value from the unarchiver is an autoreleased object, so will be released when the pool is drained. This is before the applicationDidEnterBackground call is made.
The correct solution is to retain the value from the unarchiver. Viz:
if (data == nil)
arrayImage = [[NSMutableArray alloc] init];
else
arrayImage = [[NSKeyedUnarchiver unarchiveObjectWithData:data] retain];