Preferred way of storing data in OS X/Cocoa? [duplicate] - objective-c

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
iOS store just a little bit of data
New OS X dev here. I have a modicum of user data I need to store (just paths to recently opened files, really). What is the preferred way of storing these in Cocoa land? I've heard of Core Data before but, as a Windows dev who has encountered tons of APIs from MS like this, does anyone actually use this?
I could just write everything to my own file, of course, but I'd prefer to do things The Right Way(TM).
Any suggestions would be great!

If your application is document based, the list of recently opened files is automatically stored for you. If you need to store them yourself, then I would suggest using NSUserDefaults. It is the most common way to store lightweight information such as preferences and recently used items.
Yes, people do use core data, but it is usually used for more complex data, such as a document with different parts.

See my answer to this thread for five suggestions for storing data. Although that thread covers iOS and therefore Cocoa Touch instead of Cocoa, the answers are all pretty much the same.
Note that the first answer, NSUserDefaults, is meant for saving data like app preferences. That might be most appropriate if the application will always want to load the same set of data; if the data is more like a document, where you might have different sets of data stored in different files, you should use one of the other methods. Writing a property list would probably be simplest in this case:
// store some words in an array and write to a file at pathToFile
NSMutableArray *array = [NSMutableArray array];
[array addObjects: #"foo", #"bar", #"baz", nil];
[array writeToFile:pathToFile];
// (later) read contents of the file at pathToFile into a new array
NSArray *words = [NSArray arrayWithContentsOfFile:pathToFile];
As for Core Data, yes, many people use it. It's a very nice way to manage persistent objects. However, it sounds like it's way more than you need for just storing a bunch of paths.

As ughoavgfhw mentioned, the NSDocument architecture already takes care of keeping a list of recent documents. (If you look through your Preferences folder, the *.LSSharedFileList.plist preference files hold this data).
If you take a look at those files in Property List Editor or Xcode 4, you'll see the preferred way to store a reference to a file in a persistent manner is to use Alias (or "Bookmark") data. If you're coming from a Windows/*nix background, alias data can keep track of an item even if it's renamed or moved.
If you need to store a list of recent files by yourself, and can require OS X 10.6+, you can use NSUserDefaults, along with the bookmark data functionality found in NSURL.
In your method that opens files, you could do something like this:
NSString * const MDRecentDocumentsKey = #"MDRecentDocuments";
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
// assume single item
NSURL *URL = [NSURL fileURLWithPath:[filenames objectAtIndex:0]];
NSMutableArray *recentAppBookmarks =
[[[[NSUserDefaults standardUserDefaults] objectForKey:MDRecentDocumentsKey]
mutableCopy] autorelease];
// assume 20 item limit
if ([recentAppBookmarks count] + 1 > 20) {
[recentAppBookmarks removeLastObject];
}
NSData *data = [ bookmarkDataWithOptions:0 includingResourceValuesForKeys:nil
relativeToURL:nil error:NULL];
[recentAppBookmarks insertObject:data atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:recentAppBookmarks
forKey:MDRecentDocumentsKey];
}
To get the list of recent files at app launch, you could do something like this:
- (void)awakeFromNib {
recentAppURLs = [[NSMutableArray alloc] init];
NSArray *recentAppBookmarks =
[[NSUserDefaults standardUserDefaults] objectForKey:MDRecentDocumentsKey];
for (NSData *bookmarkData in recentAppBookmarks) {
NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithoutUI|NSURLBookmarkResolutionWithoutMounting
relativeToURL:nil bookmarkDataIsStale:NULL error:NULL];
if (resolvedURL) [recentAppURLs addObject:resolvedURL];
}
}
Otherwise, if you need compatibility with OS X 10.5 and earlier, I posted some categories on NSString in this answer.

Related

Does NSMutableDictionary now truncate data as a string or add ellipses? It seems to, here

Does NSMutableDictionary now truncate data as a string, or return ellipses for long data? I use this feature to save a plist with different colors in it. This has worked fine (with minor modifications) since around 2005.
But last month, I think after an OS update, I noticed all of my data was starting to get corrupted. I've narrowed it down to this. When I run this code...
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSError *error = nil;
[dict setObject:[NSKeyedArchiver archivedDataWithRootObject:[NSColor redColor] requiringSecureCoding:NO error:&error] forKey:#"backdropColor"];
NSString *test = [dict description];
Note that before MacOS 10.13, you can use this code, which has the same bug.
[dict setObject:[NSArchiver archivedDataWithRootObject: [NSColor redColor]] forKey:#"backdropColor"];
When I run either, I get the following result:
backdropColor = {length = 3576, bytes = 0x62706c69 73743030 d4010203 04050607 ... 00000000 00000d88 };
See the ... ? That's not supposed to be there. It used to fill in that ... with all of the data.
I can't find any documentation that explains a change, and while this code has remained unchanged for years, it's now corrupted months of work for one of my users already.
Turning some of our comments into an answer:
-[NSObject description] is not meant to be a general-purpose parsing/serialization format, and over time, the descriptions of objects may change. In macOS Catalina, the description for NSData changed to truncate contents in the middle to avoid full display of enormous data blobs.
Currently, I throw a ton of objects into NSData and then export that using the description to a plist, which can then be easily parsed back into an NSData object later. That's all it needs to do, correct dump an NSData object out and read it back in.
Based on your minimal requirements, the simplest resolution for your problem is simply storing NSData objects directly in your plist, instead of their -descriptions. The plist format natively supports binary data, and all Foundation tools (like NSPropertyListSerialization) will accept NSData instances directly for writing out to disk.
If you would like to explicitly convert your binary data into a safely round-trippable string, consider converting it to a base64-encoded string using -[NSData base64EncodedStringWithOptions:], storing the string in the plist, and retrieving later with -[NSData initWithBase64EncodedString:options:].
However, if you require backwards compatibility with the old format (e.g. versions of your app running on macOS Catalina and newer must be able to save files readable on older versions of macOS and your app), you will need to write your own method for replicating the format.

How to take parameters from plist?

I am quite new at Objective-C programming, I was asked to develop a framework that could be implemented in IOS apps. This framework has three methods (that take a model object as an argument) that perform API comsumption and return a message (that takes from response). The problem is that I was asked to store the module parameters in plist, and I don´t have a good clue what this means. I been reading about plist and I know they can store serialized objects. But I really don´t understand what it means to be storing all parameters on this file.
A plist is essentially a dictionary (or NSDictionary) -- with keys and values -- written to a specific file format that iOS expects.
To write a plist file is easy when you do it from Xcode. In Xcode 10.3 you can go to "File" -> "New" --> "File..." and select "Property List" from the types of files you see:
I created a file (as an example) named "SomeFile.plist" and then added a couple keys & values to it:
Now after you get this file included in your new project, you need to read the keys & values back in. Here is a related question that shows you different ways to read the plist / dictionary, such as:
NSString *path = [[NSBundle mainBundle] pathForResource: #"YourPLIST" ofType: #"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: path];
NSString *name = [dict stringForKey: #"RaphaelName"];

coding efficiency vs execution efficiency

So I have these two methods:
-(void)importEvents:(NSArray*)allEvents {
NSMutableDictionary *subjectAssociation = [[NSMutableDictionary alloc] init];
for (id thisEvent in allEvents) {
if (classHour.SubjectShort && classHour.Subject) {
[subjectAssociation setObject: classHour.Subject forKey:classHour.SubjectShort];
}
}
[self storeSubjects:subjectAssociation];
}
-(void)storeSubjects:(NSMutableDictionary*)subjects {
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *subjectsList = [documentsDir stringByAppendingPathComponent:#"Subjects.plist"];
[subjects writeToFile:subjectsList atomically:YES];
}
The first loops through an array of let's say 100 items, and builds a NSMutableDictionary of about 10 unique key/value pairs.
The second method writes this dictionary to a file for reference elsewhere in my app.
The first method is called quite often, and so is the second. However, I know, that once the dictionary is built and saved, its contents won't ever change, no matter how often I call these methods, since the number of possible values is just limited.
Question: given the fact that the second method essentially needs to be executed only once, should I add some lines that check if the file already exists, essentially adding code that needs to be executed, or can I just leave it as is, overwriting an existing file over and over again?
Should I care? I should add that I don't seem to suffer from any performance issues, so this is more of a philosophical/hygienic question.
thanks
It depends.
You say
once the dictionary is built and saved, its contents won't ever change
until they do :-)
If your app is not suffering from any performance issues on this particular loop I wouldn't try to cache for the reason that unless you somehow remember that you have a once-only write on the file you are storing up a bug for later.
This could be mitigated by using an intention revealing name on the method. i.e
-(void)storeSubjectsOnceOnlyPerLaunch:(NSDictionary*)subjects
If I got my time back for tracing down bugs caused by caching, I would have several days back in my life.
Your solution is totally over engineered, and has tons of potential to go wrong. What if the users drive is full? Does this file get backed up? Does it need backing up / are you wasting the users time backing it up? Can this fail? Are you handling it? You are concentrating on the entering and storing of data, you should be focusing on accessing that data.
I'd have a readwrite property allEvents and a property eventAssociations, declared readonly in the interface, but readwrite in the implementation file.
The allEvents setter stores allEvents and sets _eventAssociations to nil.
The eventAssociations getter checks whether _eventAssociations is nil and recalculates it when needed. A simple and bullet-proof pattern.

Where to store strings for iOS app [duplicate]

This question already has answers here:
Constants in Objective-C
(14 answers)
Closed 8 years ago.
I have an iOS app that requires me to have a "bank" of multiple strings. What I mean is that I need to have several strings that I can call upon at any time. Here is what I am thinking of.
// Strings.h
#define STR_ONE #"1"
#define STR_TWO #"2"
// ...
And when I need to use these strings, I simply include the header file. I chose to go with a header file because there will be many of these strings, and I just wanted to keep them separate.
So the question: Is this the best approach to solve my problem? Are there any alternate (and better) ways that I am missing?
Side notes: Is there any memory management I need to be thinking about here?
Should this be written to a file, and drawn upon from there?
Thankyou
NSArray: you can store a fixed amount of string insiden an array
NSArray* nameArr = [NSArray arrayWithObjects: #"Jill Valentine", #"Peter Griffin", #"Meg Griffin"
NSMutableArray: this type of array can expand and decrease in size.
NSMutableArray *names = [[NSMutableArray alloc] init];
[self.names addObject:#"Harry Potter"];
If the amount of Strings is not enorm, a simple Plist will work for you. But i also would recommend you to read about core data.
Property List Link

Objective-C - Json for Game or is there a better way

i have some questions about json! i hope anybody can help me!
I have created a game and now i want to bring some variables out of my Game into a json file!
So i want to ask, is it possible to bring floats and an array
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(50.0, 150.0)],
[NSValue valueWithCGPoint:CGPointMake(350.0, 300.0)],nil];
into a json file?!
Also i want to bring my background-images for the different levels in the json file.
Is it possible to do it with json or anybody know a better way.
It would be great if anybody could tell me. Or anybody know a good tutorial?!
Thanks
If you don't have to serialise the objects to move between environments, then I wouldn't bother with JSON... if you are consuming a web service or something, then they are great... if you have them in values, then I would just store them in a plist assuming you have no interchange.
To answer the question about the background images ... you would simply store the filename/path to the image. That way, multiple files or levels could potentially refer to the same asset.
JSON supports hashes (dictionaries) and arrays as containers, which can contain hashes, arrays, numbers, strings and booleans within them. So to capture that data you might have a structure like this:
{
"points":[
[50.0, 150.0],
[350.0, 300.0]
]
}
And read it back with something like this:
NSArray *pointsData = [json objectForKey:#"points"];
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(
[[pointsData objectAtIndex:0] objectAtIndex:0],
[[pointsData objectAtIndex:0] objectAtIndex:1]
)],
[NSValue valueWithCGPoint:CGPointMake(
[[pointsData objectAtIndex:1] objectAtIndex:0],
[[pointsData objectAtIndex:1] objectAtIndex:1]
)],
nil];
Cocoa supports only these type in its JSON support.
NSNumber (integer, float, boolean)
NSString
NSArray
NSDictionary
Whatever you want to save/load, you should organize your data to use only those types. Direct use of NSValue is not supported, so you need to make something like this.
NSDictionary* point = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:444f],
#"x",
[NSNumber numberWithFloat:555f],
#"y",
nil];
And save the dictionary.
In addition, you also can use equivalent new literal syntax which is mainly designed for Property List.
NSDictionary* point = #{ #"x" : #444f, #"y": #555f };
This is equal to above example.
CAVEAT: All keys of NSDictionary must be NSString when you are saving the data to JSON or Property List.
Consider using of Property List
If you are targeting only iOS, Property List is always right and better choice to save/load some small amount of variables. Also Xcode will give you better editor.
Support for JSON in Cocoa is just a subset of the Property List, and mostly similar. So look at the Property List first, and than you will be able to use PLIST or JSON easily. (this is why there's no guidance document for JSON in Cocoa)
Background Images
It is possible to save image data in JSON or Property List, but it is usually not recommended. Because JSON or PLIST need to load at once. This means all file content must be loaded into memory. If you put multiple images in a JSON or PLIST file, it will get bigger up to multiple megabytes easily. And iOS devices doesn't have much memory, so your program may crash.
So it is recommended to store only path to image file in JSON or PLIST file - as like #JoelMartinez indicates -, and load the each image file directly from the path.