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"];
Related
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.
I'm getting the following exception when trying to unarchive when using Swift:
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (NSKnownKeysDictionary1) for key (NS.objects); the class may be defined in source code or a library that is not linked'
The context: I'm creating a "Share Links" extension. In my main app (written in Objective C) I write out an array of dictionaries with the information about the links using MMWormhole.
NSFetchRequest* bookmarkFetch = [NSFetchRequest fetchRequestWithEntityName:#"XX"];
bookmarkFetch.propertiesToFetch = #[
#"name", #"text", #"date", #"url"
];
bookmarkFetch.resultType = NSDictionaryResultType;
NSArray* bookmarks = [moc executeFetchRequest:bookmarkFetch error:NULL];
[wormhole passMessageObject:bookmarks identifier:#"XXX"];
The values in the array are NSStrings and an NSDate.
In the bowels of MMWormhole you get:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject];
messageObject is just the bookmarks array without any intermediate processing.
In the extension I have:
let wormhole = MMWormhole(applicationGroupIdentifier: "group.XX", optionalDirectory:nil)
let bookmarks = wormhole.messageWithIdentifier("XXX") as? Array<Dictionary<String,AnyObject>>
messageWithIdentifier: ends up calling this ultimately:
id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
The array is written out to the app group folder correctly -- I can read it using another extension, one written in Objective C.
This exception appears when I run in the Simulator. The code appears to work correctly when run on a 32-bit device (iPhone 5 and iPad 3). I don't have a 64-bit device to test on currently.
I imagine I'm missing an import or a framework, but which one(s)?
This is just a side note:
You can set class names for both the NSKeyedArchiver & NSKeyedUnarchiver.
I had this problem without dealing with CoreData at all. The unarchiver did not find my own class anymore.
Setting the className for my class works as shown below:
For the archiver:
NSKeyedArchiver.setClassName("MyClass", for: MyClass.self)
let data = NSKeyedArchiver.archivedData(withRootObject: root)
And the unarchiver:
NSKeyedUnarchiver.setClass(MyClass.self, forClassName: "MyClass")
let root = NSKeyedUnarchiver.unarchiveObject(with: data)
I asked this on the Apple Developer forums and (for once) got a good answer from Apple Developer Relations. I'll quote the important bits:
NSKnownKeysDictionary1 is a Core Data voodoo that I do not understand.
... Clearly something is going wrong with its serialisation and
deserialisation. Do you have Core Data up and running on both ends of
the wormhole? Regardless, it might make more sense to do a deep copy
of your bookmarks array (and anything else you get back from Core
Data) so that you’re sending standard dictionaries across the ‘wire’
rather than Core Data stuff.
So my solution is either to add the Core Data framework to the extension or to do the deep copy. (I've temporarily done the former. The proper solution is probably the latter.)
Nothing is written in my plist file after this code. What is wrong with my code?
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"aFile.plist"];
NSMutableDictionary *reqData = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
/*
some modifications to "reqData"
*/
[reqData writeToFile:finalPath atomically:YES];
Nothing is written in file. what could be the problem?
You'd better write to Document folder:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *path = [paths objectAtIndex:0];
Update
According to NSDictionary Class Reference:
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
Is there any objects with a type other than these valid ones in your whole dictionary?
You are trying to write the file back to the app bundle.
That's what's most likely causing the error.
What if you try writing it somewhere else (e.g. in your desktop folder)?
If you're including the plist with your application, you'll want to copy that file into the Documents directly when the app first starts up (if it hasn't already been copied there). Then, any read and write operations you want to do on the plist should be done from the copy in the Documents directory instead of the version in the app bundle.
First Thing to remember:
You can read a plist file from resources but you can't modify it.
if you want to modify,
copy that file to Documents directory
copy contents of plist into array or dictionary depending on its type
make changes you want
Finally.... save it back to documents directory
that do the trick
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.
How do I open a .string file as an NSDictionary?
Edit: I want to be able to use it like this:
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:#"dict.plist"];
NSDictionary *strings = [[NSDictionary alloc] initWithContentsOfFile:#"Strings.strings"];
If you really want it in a dictionary, you can load it using [NSDictionary dictionaryWithContentsOfFile:], since it is in “Old-style ASCII” format. I have used this technique before on Mac OS X, but I'm not sure you can do the same for iOS.
However, if you want a translation for a particular string, there are at least two ways to do it:
NSLocalizedStringFromTable() will allow you to load strings from files other than the normal Localizable.strings file. Provide the name of your strings file (without the extension).
NSBundle's localizedStringForKey:value:table: method. This essentially performs the same operations as the method above, and as above, provide the name of your strings file without the extension.
.string files just store key/value pairs, like:
"StringKey" = "some localized text";
you can get the text for a specific key using NSLocalizedString. If you want to get all the strings in a file, I suppose you could read the Localizable.strings file and parse it.