Today
a fellow blogger asked me to pimp his post. Since it's only two lines, I figure I can take a break from my busy schedule of, uh, drinking and stuff, and help a brother out.
So I did the Challenge problem in Chapter 4 of
Cocoa Programming for Mac OS X, Second Edition
. I've come up with two different "solutions".
- (IBAction) reportCharacterCount: (id)sender { NSString *inputString = [inputField stringValue]; [outputField setStringValue:[NSString stringWithFormat:@"%@ has %d letters.", inputString,[inputString length]]]; } |
- (IBAction) reportCharacterCount: (id)sender { NSString *inputString = [[NSString alloc] initWithString:[inputField stringValue]]; [outputField setStringValue:[NSString stringWithFormat:@"%@ has %d letters.", inputString,[inputString length]]]; [inputString release]; } |
For pedagogical reasons, could someone tell me what the difference is between the two? And if possible, which one is better?
(As you can probably guess, my very first solution consisted of version 1 with a release, which brought me to the debugger in a hurry.)
The short answer is the difference between the two is that the second one wastes time and memory to no good effect. There are several problems with the second one: for example, if you really wanted a immutable copy of a string, you should just use
[[inputField stringValue] copy] and not
-initWithString:, because the latter
always allocates a new string, whereas the former will just return the same object with an increased retain count if the original string was already immutable. Now that's fast!
But, in fact, there's no point in doing this copy of the inputField's string, for two reasons. First off, when you call
-setStringValue: on outputField it's really the field's job to make sure it holds on to a immutable copy of the string you've passed in, so it's going to call
-copy or do something similar itself. (It's true there were bugs in early versions of NeXTstep where sometimes mutable strings would be retained or returned instead of immutable ones, but those are mostly ironed out now.)
Secondly, and more importantly, you're not actually passing this string directly to your output NSTextField, you are generating a
new, autoreleased string in your
+stringWithFormat: call, which has the inputField's stringValue as a sub-string. Now, leaving aside the actual implementation details of
+stringWithFormat:, it's a given that it will somehow keep an immutable copy of any strings you pass into it. Otherwise, honestly, nothing in this damn system would work.
Less code is better if it's functionally the same, and the second implementation is absolutely no safer in any way. Even if you were, say, messing with multiple threads at some point, so the value of inputField could change
during your action method, both implementations would be failures, so there's really no conceivable situation in which the second implementation is better.
Also, what's up with that blank line at the beginning of your methods? Seriously, that isn't helping anyone.
Finally, I should point out both implementations are really non-optimal in the post-10.3 world: what you should really do is bind the 'value' inputField to a new instance variable in your controller in Interface Builder (say,
NSString *inputString;), and then bind outputField's 'value' to your controller with the path of, say, 'outputString', then write the following:
+ (void)initialize; { [self setKeys:[NSArray arrayWithObject:@"inputString"] triggerChangeNotificationsForDependentKey:@"outputString"]; }
- (NSString *)outputString; { return [NSString stringWithFormat:@"%@ has %d letters.", inputString, [inputString length]]]; } |
Admittedly, this isn't really less code, and in fact it change the semantics of your app: eg, it doesn't require you to send the action to populate the outputField. But bindings are generally the way you should code these days; if you find yourself using target/action, or otherwise manually pushing or pulling values to or from controls, think hard about using bindings instead.