Is it possible to store an NSMutableArray together with all its contents into a file and restore it later from there? - iphone

Some kind of serialization available in iPhone OS? Is that practically possible or should I quickly forget about that?
I am making a tiny app that stores some inputs in an NSMutableArray. When the user leaves, the inputs should stay alive until he/she returns to continue adding or removing stuff to/from that array.
When the app quits, there must be some way to store all the stuff in the array in a file. Or must I iterate over it, rip everything out and write it i.e. comma-separated somewhere, then the next time go in again, read the stuff in, and iterate over the lines in the file to make an array with that data? That would be hard like a brick. How to?

The easy way, since you already have an NSArray object is to write the array to disk using
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
and read it back in with:
- (id)initWithContentsOfFile:(NSString *)aPath
or
+ (id)arrayWithContentsOfFile:(NSString *)aPath
You can also use NSCoder.
You can probably search sof for the right code.

So long as the objects in the array implement NSCoding (NSString and NSValue do; if you're storing your own objects it's relatively straightforward), you can use:
[NSKeyedArchiver archiveRootObject: array toFile: filePath];
to save and:
array = [NSKeyedUnarchiver unarchiveObjectWithFile: filePath];
to load.
You can similarly load/save to NSData.

The iPhone SDK is designed to store data using SQLite tables.

You can use NSPropertyListSeralization, since NSArray is a type of property list. You can find more information here.

Related

Best Way To Store Multiple Arrays Associated Together - Objective C

Throughout my iOS program. I create multiple arrays that all work together to make the program work. Now I need a method to store the arrays even when the iPhone is turned off. So I need these arrays to be stored like files that only I can touch.
I really would like to use User Defaults, but I just don't know if that is what I need. I have questions like:
How long will my data last in User Defaults?
When could you possibly lose the data?
If I was able to store arrays in User Defaults, how might I go about storing arrays in NSDictionaries? That way I could keep of my associated arrays together and give each array a specific key.
store array as NSData as in NSDefaults
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:urArray1] forKey:#"Array1"];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:urArray2] forKey:#"Array2"];
and read it as
NSData *dataRepresentingSavedArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"Array1"];
if (dataRepresentingSavedArray != nil)
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
I think that what you want is to store the arrays on the disk and then (when you actually need them) get them back. You can start by seeing if the objects inside your NSMutableArrays conform with the NSCoding protocol. You can then simply write them to disk and load them when you need. For that you can use NSKeyedArchiver class.
As #The Saad pointed, and well, you can store the arrays using that piece of code. Although I wouldn't recommend it. You can check #roronoa zorro answer, as well.
NSUserDefaults basically just a wrapper around property list serialization.
NSUserDefaults provides a programmatic interface to Mac OS X's
preferences system. preferences are actually stored to the disk, they
are written to the user's Library/Preferences/ directory with the file
name equal to the bundle identifier with a plist extension.
Take a look at MikeBeam's article here
Well NSuSerDefaults is a way of storing persistent data in iPhone and you don't have to worry about losing of data or about it's persisitency as long as the application remains on the iPhone.
Yes, you can store the NSDictionary object in NSUserDefaults but it is advisable to use it for small Data.For bigger data you can use sqlite or any other DB.
Here's the Tutorial For NSUserDefault

How to use my Class with PList in objective-c?

I have a Class for handling my data in my project, and now I need to store the data.
I'd like to use a Plist but I'm a bit unsure of how to start.
My class is pretty simple - 6 pieces of data, flat (no hierarchy).
I want my app to start with no data, so can I assume that I should create the PList programmatically once the User creates their first piece of data? (That is, don't create a .plist file in 'Supporting Files' prior to distribution?)
Then, when the app starts the next time, read the data and create an NSMUtableArray array of Class instances?
To create a property list, all you need to do is use appropriate types (i.e. those that support the property list format: NSData, NSString, NSDictionary, NSNumber, NSDate, NSArray), store them in a single container, and tell the containing object to write itself to a file. To read the data, you can initialize that same type using a path. For example:
// writing some data to a property list
NSString *somePath = ... // replace ... with the path where you want to store the plist file
NSMutableDictionary myDict = [NSMutableDictionary dictionary];
[myDict setObject:#"Caleb" forKey:#"name"];
[myDict setObject:[NSNumber numberWithInt:240] forKey:#"cholesterolOrIQ"];
[myDict writeToFile:somePath atomically:YES];
// reading the file again
NSDictionary *readDict = [NSDictionary dictionaryWithContentsOfFile:somePath];
The simplest way is to simple save an NSArray or NSDictionary to disk. Caleb's answer goes into detail there so I won't repeat it, other than to say you might have to convert a non-compatible object like NSColor to an property list object like NSData. It's up to you to do this each time you save or load your data.
NSKeyedArchiver and NSKeyedUnarchiver give you a little more control over the process, but work pretty much the same way. You provide (or get back) a plist compatible root object (usually an NSDictionary) that contains your data. I recommend creating a dictionary that includes your data structure as well as an arbitrary number (your app's build number is a good choice) to use as a version indicator. This way if you ever update your data model you can easily determine if you need to do anything to convert it to the new version.
If you're putting your own objects into the data file, look into NSCoding. The protocol gives you two methods using NSKeyedArchiver and NSKeyedUnarchiver to save and restore your data. This is by far the most straightforward approach if your data model consists of anything more than a few simple strings and numbers, since you're dealing with your own native objects. In your case, you would have your data class implement NSCoding and use the NSKeyedArchiver and NSKeyedUnarchiver methods to encode your six instance variables. When it's time to save or load, pack the instance of your class into an NSDictionary (along with a versioning number as I mentioned above) and call NSKeyedArchiver's archiveRootObject:toFile:. Your save an load methods deal only with your own data object, which makes things easy for you. The common pitfall to watch out for here is if your custom data object contains other custom object. This is fine, but you have to make sure every object that's going to be saved has its own NSCoding implementation.
Two things you can do:
Use NSUserDefaults:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
The objectForKey method is the one you want to use to store your class. But, as pointed out in the comments, this shouldn't really be used for storing lots of user data; it's best for saving preferences.
For storing more data, you might want to look at Core Data. It's more complex, but should be better suited to your needs. Here's a tutorial on it:
http://mobile.tutsplus.com/tutorials/iphone/iphone-core-data/
Neither of these seems best for your simple application, but I leave this answer up since it gives alternatives for saving data to the iPhone.

writeToFile fails - how do I debug WHY it fails (what tools)?

I'm getting an NSDictionary from JSON (using SBJson), and I want to store it. I'm using
[liveData writeToFile:localFilePath atomically:YES]
and it fails. The data looks like its all NSString, NSDictionary, and NSArray (which "atomically:YES" demands). I used the same localFilePath elesewhere.
So my question is: how can I find out where the problem is? What tools can I use to understand why writeToFile fails? The log doesn't show an error message.
It may have multiple reasons:
The path you are writing to is wrong, not writable (you don't have write access to it), or the parent directory does not exists (if localFilePath is "/path/to/file.plist" but the directory "/path/to/" does not exists, it will fail)
The liveData dictionary does contains objects that are not PropertyList objects. Only NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary objects can be written into a Property List file (and writeToFile:atomically: do write to a plist file so the dictionary do have to contains only PList objects for that method to succeed)
I know this is a 2 year old question. But since I just had this same problem and fixed it here's what I found. I bet your NSDictionary has some keys that are not NSStrings.
Instead of keying like:
[_myDictionay setObject:thisObj forKey:[NSNumber numberWithInt:keyNumber]];
Key like:
[_myDictionay setObject:thisObj forKey:[NSString stringWithFormat:#"%i",numberWithInt:keyNumber]];
That fixed my problem right up.
The top way can't be saved to a plist file.
Since you're getting your info from a JSON conversion, it is possible that there are some objects or keys that are NSNumbers in there. You would have to convert them. But that's a pain.
So since you already have it in json, just store it as the json string in its entirety in a #"data" key and the re-expand it when you load the plist later back into your array or dict.
I've tried saving an NSDictionary to disk with only numbers for keys and values. Switching the keys to NSString works but not when they keys are NSNumber. Do keys have to be NSString?
EDIT: I know better now that it can be any object that responds to hash; often it's NSString's, though.
Old thread : But this apple thread would be nice to read. Basically iOS has issue in Cache directory. For 1000+ folders it returns error.
I would suggest to add your own atomic mechanism to write file as a fallback.
https://developer.apple.com/forums/thread/128927

Do I need to initialize an iOS empty nested array that's part of a plist import?

the code below is working, but I want to make sure it's correct. I'm nervous about having an empty Array inside my dictionary that I create from the plist, since typically it seems that if you don't, say, initWithCapacity:1 then you often get memory errors once you start trying to add items.
At least, that's been my experience with NSMutableDictionary. However, this is the first time I'm trying to implement nested data objects, so perhaps the reason this code works is that the nested array is automatically initialized when it's imported as part of its parent dictionary?
Any and all comments appreciated. Thanks.
First, here's what the plist looks like that I'm using to create my dictionary:
Next, here's my code where I'm using the plist to create a dictionary, then adding an item to dataArray
// Create a pointer to a dictionary
NSMutableDictionary *dictionary;
// Read "SomeData.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"SomeData.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
// Now let's see if we can successfully add an item to the end of this empty nested array. How 'bout the number 23
NSNumber *yetAnotherNumber = [NSNumber numberWithInt:23];
[[dictionary objectForKey:#"dataArray"] addObject:yetAnotherNumber];
// Dump the contents of the dictionary to the console
NSLog(#"%#", dictionary);
Okay, fine, simple, good. When I Log the dictionary contents it shows that "23" has been added as an array value to dataArray. So the code works. But again, I want to confirm that I'm not "getting lucky" here, with my code just happening to work even though I'm not properly initializing that nested array. If so, then I could run into unanticipated errors later on.
So to sum up, dataArray is an empty array inside the .plist, so do I need to initialize it somehow (using, for example initWithCapacity: or something else) before I can properly populate it, or is the way I'm coding here just fine?
Thanks again.
EDIT
Hey all. I've been doing continued research on this, in the interests of finding a satisfying answer. I think I may have stumbled upon something, via this link on deep copying. His previous posts on deep copying had presented some code to do essentially what I was looking for above: create a mutable copy of a dictionary or array, from a plist, that also has mutable sub-structures.
However, as mentioned in the link above, it looks like these methods were superfluous, due to the CFPropertyListCreateDeepCopy method, which can be invoked with a call such as
testData = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, [NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);
So, my question is, can I properly use CFPropertyListCreateDeepCopy, in the way shown, to achieve what I've been asking about here? In other words, can I use this method to import my dictionary from a plist with fully mutable, nested data objects?
As I mentioned in one of the comments, I know I can create a nested, mutable dictionary manually, but for complex data that's just not practical, and it seems unlikely that built-in methods to import a mutable plist don't exist. So, based on the above, it looks like I've possibly found the solution, but I'm still too new to this to be able to say for sure. Please advise.
(Side note: I would simply test the code, but as we've established, the current SDK is buggy with regard to allow you to edit immutable nested dictionaries, contrary to the documented behavior. So as before, I'm not just interested in whether this works, but whether it's correct)
Thanks in advance.
init... methods should only be called once, immediately after a call to alloc or allocWithZone:. When framework code creates and returns an object or graph of objects, their init... methods have already been called, so sending another init... message would have undefined results. Don't do that.
Interestingly, in spite of what the documentation appears to say (and admittedly I probably missed a key sentence or paragraph somewhere), when you create an instance of a mutable collection by reading a plist, any nested collections are also mutable. I ran the following little experiment in a test harness just to be sure:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSMutableArray *cats = [pets objectForKey:#"cats"];
[cats addObject:#"Foo"]; // EDIT: Added line I accidentally omitted earlier
NSLog(#"%#", cats);
So again, the nested collections created when you read in the plist are fully initialized, and mutable to boot, so you can simply use them, as you've been doing.
EDIT
However, after doing some further reading of the docs, I think the OP is right to feel uneasy about relying on what is apparently an undocumented feature of the current version of the SDK. For example, the Property List Programming Guide states:
If you load the property list with
this call:
NSMutableArray * ma = [NSMutableArray arrayWithContentsOfFile:xmlFile];
ma is a mutable array with immutable
dictionaries in each element. Each key
and each value in each dictionary are
immutable.
So, to be on the safe side, if you need a nested collection to be mutable, you should create it yourself. For example, I'd recommend rewriting the code in the example above as follows:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSArray *cats = [pets objectForKey:#"cats"];
NSMutableArray *mutableCats = [cats mutableCopy];
[pets setObject:mutableCats forKey:cats];
[mutableCats release];
You can then safely make changes to the nested mutable collection:
[mutableCats addObject:#"Foo"];
Any object in a dictionary which is created by reading from disk will be properly initialized. You will not have to do it on your own. However, as pointed out by jlehr, contents of the dictionary should be immutable. If you want the contents of the dictionary to be mutable, you will need to change them on your own. I have no idea why your program is not throwing an exception.
I do not know why you are getting memory errors while not using initWithCapacity:1 in other situations. The following code is perfectly valid:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"object1"];
[array addObject:#"object2"];
NSLog(#"%#",array);
[array release];
If you don't specify a capacity, the array won't pre-allocate any memory, but it will allocate memory as required later.
Edit:
It is perfectly acceptable to use NSDictionary with CFPropertyListCreateDeepCopy. In Core Foundation, a CFPropertyList can be a CFDictionary, CFArray, CFNumber, CFString, or CFData. Since NSDictionary is toll-free bridged to CFDictionary, you can use it wherever a CFDictionary is asked for by casting, and vice-versa. Your code as is will give a warning, but you can suppress it by casting the dictionary and return values.
NSDictionary *testData = (NSDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)[NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);

How to save and retry reporting GKAchievement's after network failure?

Apple states that if you want to report a GKAchievement but you get a network error, the best way to handle this is to save the GKAchievement (possibly adding it to an array), then periodically attempt to report the achievement.
What is the best place to save the achievements? Would NSUserDefaults suffice, or would a property list be a better way?
When and how often should I attempt to report? On application launch, or something like every 10 minutes?
A property list can only handle specific classes (see "What is a Property List?"), which GKAchievement is not one of. NSUserDefaults uses property lists, so that's also out. GKAchievement does, however, conform to the NSCoding protocol, which means you can easily save them to disk using an NSKeyedArchiver. I would create an array of unreported achievements and read/write them like so:
//Assuming these exist
NSArray * unreportedAchievements;
NSString * savePath;
// Write to disk
[NSKeyedArchiver archiveRootObject:unreportedAchievements toFile:savePath];
// Read from disk
unreportedAchievements = [NSKeyedUnarchiver unarchiveObjectWithFile:savePath];
You can pretty much save anything in a property list (and thus NSUserDefaults) by turning it into NSData: archivedDataWithRootObject: