Why doesn't NSSet have a writeToFile method? - cocoa

I know that I can store NSSet's in an NSArray, but why isn't a dedicated writeToFile method provided? It must be a very common use case.
I just want to learn.

The writeToFile method of NSArray and NSDictionary writes the contents as property list.
The reason NSSet lacks the method is that NSSet is not property list compliant.
But since NSSet can be initialized with an array and has an allObjects property to get an array the loss is not that bad.

Related

Does valueForKey: NSMutableDictionary use copy for NSStrings?

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.

Is it necessary to use NSKeyedArchiver for standard Cocoa collections?

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.

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.

Cocoa - NSDictionary objectForKey - memory management clarification

[I have read the Cocoa memory management rules, but still want to be certain, and would like to know if this is good form.]
My class has a mutable dictionary ivar:
NSMutableDictionary *m_Dict;
...
m_Dict = [NSMutableDictionary dictionaryWithCapacity:10];
[m_Dict retain];
At some point I'll add a mutable array to the dictionary:
NSMutableArray *array = [NSMutableArray arrayWithCapacity:100];
[m_Dict setObject:array forKey: #"myArray"];
At this point, I believe that the array object has been retained by the dictionary. Therefore I am not retaining the object prior to adding it to the dictionary. Is this correct?
Later, I will access the array, for read purposes:
NSMutableArray *array = [m_Dict objectForKey: #"myArray"];
Q1. What is being returned by objectForKey? Is it a pointer to the object being held in the dictionary? Or a pointer to a COPY of the object? (I am presuming simply a pointer to the existing object is being returned.)
Q2. What has happened here, memory management wise? I am presuming that 'array' points to an object that is still retained (by the owning dictionary object) and that I do not have to retain the object to work with it. Correct?
Thanks.
Q1) The same pointer to the Objective-C array that you added before is returned. No copy is made.
Q2) Correct. All items in the array are owned by the array. If something owns something else it keeps a retain count on it.
When you remove the object from the array the retain count is reduced and the object is (maybe) deallocated.

Cocoa String Question

I have a NSString and I want to write its value to a NSMutableString. Is this valid:
NSString *temp = [NSString stringWithString:#"test"];
NSMutableString *mutable = temp;
I ask because although this seems doable, I would think that this would assign both temp and mutable to the same address space. I have this question a lot when passing values into a method or returning values from a method. Sometimes I see other people do this or create the mutable string with stringWithString or initWithString. Thanks
You can use the mutableCopy method, which creates a mutable copy of the receiver and applies to any class which adopts the NSMutableCopying protocol (of which NSString is one of them):
NSString *temp = [NSString stringWithString:#"test"];
NSMutableString *mutable = [temp mutableCopy];
This will create a mutable copy of the string, as a new string instance. In this case it doesn't apply, as temp is an autoreleased string, but you would otherwise need to release the old string that you have made a copy of, if you no longer need it.
Since mutableCopy contains "copy", then you need to memory-manage the new string (you take ownership of it according to the Apple Object Ownership Policy).
The method that you have used simply assigns mutable as a pointer to the previously instantiated NSString.
One thing that seems to confuse a lot of people with Cocoa is the difference between variables and objects.
You have to keep in mind that when you declare a variable like NSMutableString *mutable, you are not creating a string. This is just a pointer variable of type NSMutableString*. Structurally, it's the same as any other pointer — and, as the term "pointer" implies, it just points to something that actually lives elsewhere. Because of this, you technically can assign it to point to any object. But you're not turning that object into a mutable string — you're just lying about what kind of object the variable points to. Once you try to send the object a message that only NSMutableString can respond to, the jig is up!
As Perspx said, if you have a string that you want to mutate, you can use the mutableCopy method to get — you guessed it — a mutable copy. There are also a lot of NSString methods that don't mutate the string, but let you get a new string with certain changes made (for example, stringByAppendingString:). You can go a pretty long way with those.

Resources