Is it necessary to use NSKeyedArchiver for standard Cocoa collections? - cocoa

I recently wanted to store and retrieve NSArrays from user defaults, and I found and used the suggestion at Storing custom objects in an NSMutableArray in NSUserDefaults, which works fine.
In retrospect, however, it would seem this is overkill if you are using standard collection classes, not custom objects. Can anyone confirm that the main benefit of the proposed solution I am using (linked above) is that you can store custom classes in this way, but that for working with NSArray where the contents are strings or other arrays or standard dictionaries, this approach with the NSKeyedArchiver is unnecessary?

Yes, see the NSUserDefaults documentation at:
setObject:forKey:
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You only need to use an NSCoder like NSKeyedArchiver if you need to covert your object to NSData.

Related

Unable to determine type in mono-runtime when NSArray is passed to the xamarin layer from native ios layer

We have an NSDictionary containing an NSArray being passed to the Xamarin layer from the native ios code. But as NSArray is weakly typed, converting it into a strongly typed data (to add to json structure) is proving to be a problem as type-casting to a particular data-type is not possible.
For example, the NSArray might contain an integer, a dictionary and a string and hence I am unable to determine a type in such a case.
Is there a way out to creating a proper valid json with strongly types data types in such a scenario? Any help would be greatly appreciated. Thanks!

Save a dictionary<String, [customStruct]> to userDefaults

How do I save a dictionary to the NSUserDefaults? Whenever I try to I receive an error message which says that my dictionary doesn't conform to the 'AnyObject' protocol.
NSUserDefaults.standardUserDefaults().setObject([String: [customStruct]](), forKey: "someKey")
From the Apple documentation (you should read it):
"The NSUserDefaults class provides convenience methods for accessing common types such as floats, doubles, integers, Booleans, and URLs. A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData."
So if customStruct is not one of these you will need to convert it to one of these, probably NSData or NSString.

Archived NSData, isEqualToData: and Empty Strings

I have a custom object containing several NSString objects, some ints and a few bools. I am using NSKeyedArchiver to archive a copy of the object into an NSData object.
The user than makes changes to the object variables, which are connected to an IB interface.
After the changes are made, the new version of the object is archived into a second NSData object.
These two objects are compared using
[myNSData1 isEqualToData: myNSData2];
In most cases it works perfectly well, but there is one very troubling situation:
Let's say the object had a variable initialized as follows:
NSString *myString = #"";
After the object was archived into myNSData1, we called the following:
myString = [myNSTextField stringValue];
Logging myString to the console reveals that the value of myString is still
#""
and thus has not changed value.
We now archive the object into myNSData2.
Upon executing the comparison statement above, however, it now returns FALSE. It ONLY returns FALSE if the original assignment of #"" is replaced with the #"" contained in the textfield using stringValue.
Why is this?
Have a look at the types of those strings (NSLog([myString className]) should work), because NSString is a class cluster. I'm guessing that you'll find that and one of those strings is an NSCFString, and the other is an NSCFConstantString. The archiver encodes type information, so if the types are different, the NSData will also be different.
I wouldn't rely on the NSData objects being identical. If you want to compare the two, you'd be better off unarchiving them and using isEqual:. That way, you'd have full control.

NSData from a custom object

I'm working on client/server application which uses AsyncSocket. For transferring data, it uses NSData.
How can I insert my custom object, containing NSNumbers, NSIntegers, and NSStrings into an NSData object and then get it back out?
One way to insert (serialize) a custom object into an NSData object is to use NSCoding and NSKeyedArchiver.
First, have your custom object implement the NSCoding protocol.
Example here:
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Archiving/Articles/codingobjects.html#//apple_ref/doc/uid/20000948-97234
Then, for information on using your object with NSKeyedArchiver refer to:
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Archiving/Articles/creating.html
Hope that helps!

NSManagedObject as NSDictionary key?

In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.

Resources