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.
Related
I have an NSMutableDictionary with a dozen keys, each key refers to an NSString value. If I call [myDictionary valueForKey:#"abc"] I will get an NSString object.
Is this NSString copied?
As there is no #property declaration, what happens with this retrieval of the string? I assume I get a copy. Is it any different with a NSMutableString?
My purpose is that I have an array of NSDictionaries. I call valueForKey on the array to get all the #"abc" keys from the collection of dictionaries. Thus I now have an array of NSStrings.
I need to pass that array to an NSOperation on a background thread. Thus, the NSStrings need to be copies so that modifying the dictionaries later will not cause the ones passed to the NSOperation to get changed.
From the documentation of -[NSDictionary valueForKey:]:
If key does not start with “#”, invokes objectForKey:. If key does start with “#”, strips the “#” and invokes [super valueForKey:] with the rest of the key.
From the documentation of -[NSMutableDictionary setObject:forKey:]:
If aKey already exists in the dictionary, anObject takes its place.
valueForKey: does objectForKey: and does not copy the object. setObject:forKey: replaces the object, it does not modify the object. If you do
[mutableDictionary setObject:#"klm" forKey:#"abc"];
a = [mutableDictionary valueForKey:#"abc"];
[mutableDictionary setObject:#"xyz" forKey:#"abc"];
now a is the old value #"klm".
NSDictionary keys are copied when set. In the code dictionary[key] = value the key object is copied, and the dictionary retains the copy. The value object is not copied; the caller and the dictionary share a reference to the same object.
When retrieving keys or object, there is no copying. dictionary.allKeys returns an array of the key objects in the dictionary, not copies. Similarly, dictionary[key] returns a reference to the value for key in the dictionary.
And just so you know, the #property (copy) only applies when setting the value. ObjC getters never copy objects, unless it is specifically documented that way or you write your own so it does that.
Similarly, and to get to your question, key-value method use the same underlaying rules for that property. So -setValue:forKey:, -setValue:forKeyPath:, -valueForKey:, -valueForKeyPath: etc. will get or set the proper value as if you accessed the property directly. In other words, if the value is copied when the property is set, -setValue:forKeyPath: will copy the value. If an accessor does not copy the value, then -valueForKey: will not copy the value.
I am reading "Core Data Programming Guide". It contains this text:
You must, however, change attribute values in a KVC-compliant fashion.
For example, the following typically represents a programming error:
NSMutableString *mutableString = [NSMutableString stringWithString:#"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:#"Laura"];
For mutable values, you should either transfer ownership of the value
to Core Data, or implement custom accessor methods to always perform a
copy. The previous example may not represent an error if the class
representing the Employee entity declared the firstName property
(copy) (or implemented a custom setFirstName: method that copied the
new value). In this case, after the invocation of setString: (in the
third code line) the value of firstName would then still be “Stig” and
not “Laura”.
Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?
Question regarding copy and programming practice:
From what I have read here:
NSString property: copy or retain?
I understand
that using copy will ensure that firstName is "Stig", not Laura
it is wise to do so because "in almost all cases you want to prevent mutating an object's attributes behind its back"
I would really like to know what is the above quoted text trying to tell us in the context of Core Data. We have to use "copy" anyway whether using Core Data or not. Also, I would be glad if someone could throw more light on point "2" (it is wise to...) above as in what will be the consequences of mutating an object's attributes behind its back?
your "Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?"
mis-matched the point that Apple document wants to explain, I believe.
As Apple document points out, if custom-accessor-method is implemented normally, the default implementation does NOT copy attribute values. If the attribute value may be mutable and implements the NSCopying protocol (as is the case with NSString, for example), you can copy the value in a custom accessor to help preserve encapsulation (for example, in the case where an instance of NSMutableString is passed as a value).
Here is a copying setter snippet
#interface Department : NSManagedObject
{
}
#property(nonatomic, copy) NSString *name;
#end
#implementation Department
#dynamic name;
- (void)setName:(NSString *)newName
{
[self willChangeValueForKey:#"name"];
// NSString implements NSCopying, so copy the attribute value
NSString *newNameCopy = [newName copy];
[self setPrimitiveName:newNameCopy];
[self didChangeValueForKey:#"name"];
} #end
The issue is when to use (and how) immutable values.
Since core data use KVO heavily when detecting changes done to objects, if you use a mutable property that is changed directly through it object and not through the property, CoreData will not detect the change to the object and your changes might not persist to the store.
If you use mutable NSManagedObject attributes, override the setter/getter method and use only them to mutate the underlying object (this mean that you are responsible to let CoreData know that a change did happen to the object, and it must be persisted to the store.
Also, if you use transformable properties for complex objects, you must trigger the change notifications yourself in order for CoreData to realise that a change has occurred, and the object should be re-transformed and saved when the context saves.
I would highly recommend that when it comes to simple objects like strings, you use immutable property values which will force you to go through the object properties and trigger the default KVO notification (copy attributes will also force the KVO notifications).
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.
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.
All objects used as keys in NS(Mutable)Dictionaries must support the NSCopying protocol, and those objects are copied when they're used in the dictionary.
I frequently want to use heavier weight objects as keys, simply to map one object to another. What I really mean when I do that is effectively:
[dictionary setObject:someObject forKey:[NSValue valueWithPointer:keyObject]];
("When I come back and hand you this same key object instance again, get me that same value out.")
...which is exactly what I end up doing to get around this design sometimes. (Yes, I know about NSMapTable in desktop Cocoa; but e.g. iPhone doesn't support this.)
But what I don't really get is why copying the key is necessary or desirable in the first place. What does it buy the implementation or caller?
The copy ensures that the values used as keys don't change "underhand" while being used as keys. Consider the example of a mutable string:
NSMutableString* key = ...
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject: ... forKey: key];
Let's assume that the dictionary did not copy the key, but instead just retained it. If now, at some later point, the original string is modified, then it is very likely that you are not going to find your stored value in the dictionary again even if you use the very same key object (i.e., the one key points to in the example above).
In order to protect yourself against such a mistake, the dictionary copies all keys.
Note, by the way, that it is simple enough to define -copyWithZone: as just doing return [self retain]. This is allowed and good code if your object is immutable, and the NSCopying contract is specifically designed such that the object returned has to be (sorta, kinda) immutable:
Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable.
(from NSCopying Reference)
and
The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.
(from -copyWithZone: Reference)
Even if your objects are not immutable, you might get away with that implementation if you only ever use identity-based equality/hash implementations, i.e., implementations which are not affected in any way by the object's internal state.
If you want to store pointers as keys then you'll need to wrap them in a NSValue object with +valueWithPointer:.
Since iOS 6 if you want to use pointers as keys, you can use the NSMapTable object, see http://nshipster.com/nshashtable-and-nsmaptable/
You can specify whether keys and/or values are stongly or weakly held:
NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
valueOptions:NSMapTableWeakMemory];
Another option that could be appropriate sometimes is to use NSCache, which holds keys strongly and is actually thread-safe.