Saving lots of dictionaries and arrays - objective-c

My game comes with lots of .plist files containing many dictionaries, arrays, etc. They are about each party member's stats, info, etc.
A singleton is in charge of reading all those files and provide the necessary data for my game to run.
The game, at many points, will edit such values from the singleton, like a hero's level, stats, etc.
Eventually, the player will be given the option to "save" the game. My original idea was to basically tell the singleton to "overwrite" the .plist files within the project with the new edited data (the new data might have changes in stat entries etc, or even brand-new dictionaries representing new party members etc) and done.
But, I don't know how to do that, or even if it is possible.
I heard about NSUserDefaults, but I don't know if it suitable for what I am aiming, considering that the amount of dictionaries and arrays will be quite big, and that it is possible to keep adding up more dictionaries and arrays in the future. Imagine that the party can have as many members as you wish, for instance.
*Note: there are no multiple save files/profiles. When you press save, it saves, without asking anything else.

NSUserDefaults is not suitable for any substantial amount of data. Just write the plist to the documents directory, specify atomic:YES to insure the entire file is written.
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
If YES, the array (or dictionary) is written to an auxiliary file, and
then the auxiliary file is renamed to path. If NO, the array is
written directly to path. The YES option guarantees that path, if it
exists at all, won’t be corrupted even if the system should crash
during writing.
This method exists for both NSArray and NSDictionary,
Obtain the document directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
BOOL status = [NSDictionary writeToFile:filePath atomically:YES];
NSDictionary could have ten NSArray depending on the top element of the plist.
Consider using CoreData.

Related

Populating a UITableView with Arrays of strings

I'm trying to create a save & load module in my app. So the user can save some specific info and then load it at any point in time.
What I'm saving is an array of NSMutableStrings. I'm creating an iPad app so the saved info will be presented in a popover with a TableView.
So, here's the question. I would like to populate that tableView with my saved arrays, and for that I need to store my arrays somewhere. I'm new to objective-c and programming in general, and would like your advice on what's the best way of doing that.
Should I use a plist to store those arrays with Keys, so the keys will be the names that the user introduces when saving and also the name of the tableView cells? or maybe a simple NSMutableDictionary will do?
Please advise.
This answer might help you. Using Core Data might be overkill here, and saving to a plist is extremely easy. This is Benoît's answer from the linked question:
NSString *error;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"yourFile.plist"];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:rootObj
format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
if(plistData) {
[plistData writeToFile:plistPath atomically:YES];
}
else {
NSLog(#"Error : %#",error);
[error release];
}
where rootObj is the object you wish to serialize, in this case, your array/dictionary.
Also, if you are going to be read/writing to this data frequently during the course of your app, using a singleton which can make a huge difference performance-wise instead of constantly reading/writing from/to a file. You can load all of your data into the singleton on app-launch and save it upon applicationWillTerminate.
To answer your array/dictionary question, it depends on how you are obtaining your strings and how you want them to be ordered. Sorting an array is extremely easy, as you can see in this answer.
Sergey, based on your description, I would advise you look into using Core Data for data storage.
https://developer.apple.com/library/mac/#documentation/cocoa/conceptual/coredata/cdprogrammingguide.html
In addition to storing data, Apple has extended the use of Core Data in all sorts of neat ways, including the use of stored data to populate a UITableViewController through the use of NSFetchedResultsController
https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html
That's it, give that a read and you should be well on your way.

How much is too much to store in NSUserDefaults?

NSUserDefaults is such an easy and convenient way to persist data on a device, and while it is encouraged as use for basic settings, I wonder, is there a practical limit to what you should use it for?
Suppose you have a large dictionary of many objects, 1000, and each of those objects is itself a dictionary with simple text strings as values. Overall, this big dictionary is probably not too large, since it only contains text, even if a fair amount of it.
Is it O.K. to use NSUserDefaults for something like this, your main data model?
Why not just store that as an NSDictionary in a file?
See this post for a nice way to save your file as a plist:
Save NSDictionary to plist
and getting it back is as easy as this:
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:fileName];

Which method is better to save data nsdictionary or plist?

I want to save data locally but i do not know which method is better nsdictionary or plist?Can anyone tell which method is better and why?
Thanks in advance!
I think maybe what you meant to ask is which is better, plist or binary. If you save an NSDictionary to file using writeToFile: it will be stored as a plist. So in that sense there is zero difference between the two.
However, you also have the option of converting an NSDictionary instance into a serialized NSData representation, and then storing the serialized data as a binary file.
As for which is better and why, that depends upon what you want to do. The API for converting an NSDictionary to/from a plist file is more convenient to use than the API for serializing and reconstructing to/from NSData. However, storing an NSDictionary as a plist file only works if everything in the dictionary is a plist object (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) and if all of your keys are of type NSString. Otherwise your writeToFile: call will fail with frustratingly little information being provided about why.
So if your data structure meets the requirements for being stored as a plist and you cannot foresee it ever being changed such that it would no longer meet the requirements then you may find it more convenient storing it as a plist. Otherwise your only option is to do a binary serialization and storage using NSData. You may prefer this latter option if you want to have code that cannot be accidentally broken by someone sticking a non-plist-object in your data structure.

How to find file types in objective c

I have done a lot of research on this and haven't found anything useful. I want am making a simple program in objective c for personal use that opens files and gives information about them. The only problem i have encountered is that i cannot find the file of a file i am opening. Without simply looking at the extension, is there a way to find the full file format of a file in objective c?
If possible, i would also like to be able to save that file in a different format. Information on this subject is also important for this application. Help will be greatly appreciated.
Mac OS X has type information attached to each file which specifies what the type of the file is supposed to be. This information is given by the application which last saved the file, so it is not necessarily correct. Also, new versions of OS X ignore this information and go entirely off of the file extension. However, the information is still stored and can be retrieved using NSFileManager's attributesOfItemAtPath:error: method.
As mentioned by quixoto above, OS X now maps extensions to UTIs. The UTI of a file can be retrieved using NSWorkspace, which can also tell you what the UTI means. This code will get the localized description of the file at /full/path/to/file:
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSString *description = [ws localizedDescriptionForType:[ws typeOfFile:#"/full/path/to/file" error:nil]];
The typeOfFile:error: method requires an absolute path.
Consider: what do you mean by "full file format"? What kinds of files and level of detail do you care about?
You can't really get "format" information from the file system like you want. Believe it or not, the file system has no idea what format files are in.
File systems traditionally store files as a stream of bytes with a name, a size, and some other attributes ("hidden", permissions, etc). That's all that it's responsible for, and it's the application's problem to read those bytes and interpret them to mean something useful.
The extension is the traditional hint to an application about what a file contains, but as you might guess, it's certainly not a verified guarantee.
Modern Mac OS X has Quick Look, which uses a system-wide framework that adds some smarts on top of this, with mappings between extensions and UTIs, which are a richer notion of file type. Docs are here. I don't think there's a way to query this system for the mappings between extensions and UTIs, but I'm not sure.
Loading a file of one format and saving in another is 100% dependent on the file types you're talking about, and you're going to have ask very specific questions about specific formats if you really care to accomplish this. (And that topic extends well beyond a couple built-in method calls in Cocoa.)
This information is now (since OS X 10.5) handled through UTIs.
You can get it like this:
NSURL *desktopURL = [[[NSFileManager defaultManager] URLsForDirectory: NSDesktopDirectory
inDomains: NSUserDomainMask] lastObject];
NSURL *workingdirURL = [desktopURL URLByAppendingPathComponent:#"WorkingDir" isDirectory:YES];
NSArray *docURLs = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:workingdirURL
includingPropertiesForKeys:#[NSURLTypeIdentifierKey] options:NSDirectoryEnumerationSkipsHiddenFiles
error: nil];
NSMutableArray *pdfURLs = [NSMutableArray array];
for (NSURL *docURL in docURLs) {
id resourceValue;
BOOL found = [docURL getResourceValue: &resourceValue
forKey: NSURLTypeIdentifierKey
error: nil];
if ( found && [resourceValue isEqual:#"com.adobe.pdf"]) {
[pdfURLs addObject: docURL];
}
}
You can see the docs for more information about the file properties that can be retrieved this way. There is quite a lot of information available through this method.

NSFileManager & NSFileHandle?

Do I have this right ...
To manipulate files on disk (create, copy, rename etc.) you use NSFileManager
To manipulate file contents (open, read, close etc.) you use NSFileHandle
I just want to make sure I am understanding this right.
EDIT_001
Thanks, thats what I figured Joshua, so I am assuming that by using the example below, open and close are both handled automatically by the implementation.
fileContents = [NSString stringWithContentsOfFile:fileOnDisk
encoding:NSMacOSRomanStringEncoding
error:&fileError];
gary
More or less, yes. From the docs:
NSFileHandle objects provide an object-oriented wrapper for accessing open files or communications channels.
... though NSFileHandle isn't necessary to read/write files. You can write an NSString to / read from a file with one line of code and no handle. Depends on what you want to do.
I believe, for the most part, that NSFileHandle is a wrapper around file descriptors -- good for 'relatively' low level reading or writing.
If you are just pushing the contents of a string or NSData to or from a file the associated methods on those classes are hard to beat.