View objects make bad keys. Views have state related to the screen: their frame, position in the view hierarchy, animation layers, etc. When you copy a view object, the copy won’t (always) be isEqual: to the original, because it’s not on the screen in exactly the same way.

Of course, this isn’t a complete list of every way key-copying can trip you up. But if you understand what copy means in Cocoa, and remember how NSDictionary works, you’ll be able to avoid or quickly solve any issues.

How to Document Such Behavior Better Than Apple Did

Given what we know about NSDictionary, what’s wrong with the following snippit from NSDictionary.h?

Answer: aKey needs to implement NSCopying, so it should be of type (id<NSCopying>) instead of type (id). That way, the header is self-documenting, and, if like most smart programmers, you’re using autocomplete to type out Cocoa’s long method names, the auto-completed template will be self-documenting too.

May 15, 2009

I started writing a list of ways I thought Objective-C could be improved, and I realized that many of my wishes involved more compact syntax. For example [array objectAtIndex:1] is so verbose I think it diminishes readability, compared to array[1].

I can’t quite match that brevity (can you, by using Objective-C++?), but with a one-line category, you can say, x = [array:1];.

My question is: do you find this compact “syntax” useful at all, or is it added complexity with no substantial code compression? Personally I think the latter, but the number of wishes I had involving more concise Objective-C syntax makes me wonder…

April 22, 2009

BREAKING UPDATE: Actually comparing the -absoluteURL or -absoluteString of two NSURLs that represent a file is not good enough. One may start file:///, and the other file://localhost/, and they will not be isEqual:! A work around is to compare the path of each NSURL. I’m still looking into the issue, but for now I am using the following method to compare NSURLs.

[a isEqual:b] may report NO for two NSURLs that both resolve to the same resource (website, file, whatever). So compare NSURLs like [[a absoluteString] isEqual:[b absoluteString]]. It’s important to be aware of this gotcha, because URLs are Apple’s preferred way to represent file paths, and APIs are starting to require them. Equality tests that worked for NSString file-paths may fail with NSURL file-paths.

An NSURL object is composed of two parts—a potentially nil base URL and a string that is resolved relative to the base URL. An NSURL object whose string is fully resolved without a base is considered absolute; all others are considered relative.

In other words, two NSURL objects can resolve to the same absolute URL, but have a different base URL, and be considered !isEqual:.

Checking if a Cocoa object is empty is a little harder then in other languages, say C++, (but easier in some ways). Because every object in Objective-C is actually a pointer to an object, there are two ways, obj, can be empty.

obj = {}

obj points to an object that is empty. Say an array with 0 items, or the string "", etc..

obj = nil

obj, the pointer obj, is NULL, nil, 0, or whatever you want to call it. You might argue that obj isn’t really an object, but it is empty, because there’s nothing in it.

Bug:

When I first started writing Objective-C, I made the mistake of writing code like: if([name isEqualToString:@""]){ ... }, to test for empty strings. And this code would work for a while until I used it in a situation where name was nil, and then, because sending any method called on nil “returns” NO, I would have mysterious errors. (Worse then a crash, because it’s harder to track down.)

Bug:

It’s tempting to avoid the previous bug, by explicitly testing for niland{}. Say with code like:

But generally this is a bad idea. It means more code, which means more places for a bug. I know it’s only one trivial test, but I’m serious, when I say it’s asking for a bug — like the bug in the example above, which sets email to @"An email address is required", whenever it is not the empty string, rather then when it is empty. (Values have been changed tho protect the innocent but it’s a bug I’ve seen.)

I’ve been using his IsEmpty() for about a year. I’ve had zero problems with it, while it’s made my code more readable and concise.

Another solution is to take advantage of what happens when you send a message to nil. (To over-simplify, you get back 0 or NO.) So you can just say “if ([obj count] == 0) then obj is empty.” This often means reversing your thinking, and testing “IsNotEmpty()” instead of “IsEmpty()”. I don’t think it’s as clear is IsEmpty() in general, but in cases where it is, there you have it.

This got me thinking about NULL/nil terminated argument lists. I don’t think they are a great idea (the compiler should be able to handle the list-termination for you!), but I think they are an especially bad idea in Objective-C.

The problem that it’s very common to have a nil object in Objective-C, relative to, say C++. Many Cocoa methods return nil on error. Since doing stuff with nil (generally) won’t cause an exception, these nils stick around much longer then in other languages. As you can see, nil is a pretty poor choice of a sentinel value.

It’s the 21st century! The compiler could tell an Obj-C method using a variable-argument-list how many arguments are in the list. This is trivial when all arguments are of type id. Since Obj-C methods use a radically different syntax from C functions, it shouldn’t effect existing C-code. Unfortunately, I don’t see this being added, because Objective-C is already so mature.

In the meantime. Be a little more suspicious of any objective-C methods taking a NULL-terminated list. I wish I had a perfect solution to avoid them, but I don’t! Sometimes they are the best way to do something. If you have a great work-around for constructing, say an NSDictionary with a variable number of key/values please let me know!