views:

550

answers:

11

Triggered by Mike Ash’s newest article “Dangerous Cocoa Calls” from his great Friday Q&A series, I want to know which are the things in Cocoa that made your code stumble. Mike has compiled a great list of evil API uses that are mostly about thread and exception safety. I’m sure that there are more hidden pitfalls that Cocoa programmers should be aware of.

To make it clear: I’m not looking for obvious programming errors, like not retaining an instance variable or sending unknown selectors to objects. I’m looking for recurring patterns that do not do what is expected. I want to find the parts of the Cocoa API where usage seems to be clear but has hidden traps.

+5  A: 

Adding a first answer to get things rolling.

Removing all subviews from a view seems an obvious task:

for (UIView* subview in view.subviews) {
    [subview removeFromSuperview];
}

This is broken because it mutates the subviews array while iterating over it.

It is not obvious though, because UIView’s subviews property returns an NSArray. One could think that an NSArray is immutable, but this is only true if it is not an NSMutableArray. Also, it’s not clear that the property does not return a copy of that subview array.

The fix is of course easy:

while ([view.subviews count] != 0) {
    [[view.subviews lastObject] removeFromSuperview];
}
Nikolai Ruhe
To be fair this is a bug that was fixed in Snow Leopard.
Mike Abdullah
This *kind of thing* is still all over in Cocoa, though. It's never a good idea to assume that any random NSArray you get from some other class is actually immutable.
Mark Bessey
@Mike: not on the iPhone ;)
Nikolai Ruhe
Mutating an array is a big no-no no matter what it is filled with.I prefer to make a copy of the subview array:for (UIView *subview in [[view.subviews copy] autorelease])and then remove them as normal.
schwa
+3  A: 

Using NSPipe repeatedly can cause problems that directly go against the documentation.

The documentation claims "You don’t need to send closeFile to this object or explicitly release the object after you have finished using it."

The problem is that if enough NSPipes get created before a garbage collection event, you will run out of file handles, and the creation of your next NSPipe will throw an exception.

Daniel Grace
+1  A: 

A lot of people don't realize that windows and applications on the Mac don't have the 1:1 correspondence that they do on Windows (and on the iPhone). An application can and will have multiple windows (including the About panel, Preferences window, etc.).

My suggestion to anybody new to programming the Mac is to make your very first toy application a document-based app, then open two documents in it. Then, you can't help but notice the two windows (one per document) in one application process.

Peter Hosey
+5  A: 

The thing I had to tell people over and over is: don't fight the framework. The most common way of fighting the framework that I saw, was developers who "didn't trust IB" and wanted to set up their UI entirely in code.

The #2 pitfall, which I see a lot of questions about today, is trying to write a Cocoa app without learning Objective-C. It seems that some people are so proud of having mastered the needless complexity of C++, that they want to do absurd things like wrap the Cocoa classes in C++ classes, and call objc_msgSend() manually.

The long and short of it is, Cocoa works very well when used as designed. When you try to use it in a manner different from the designers' intentions, you're just making yourself a lot of extra work.

NSResponder
A: 

An oversight I've noticed from new programmers is:

NSMutableString *aString = @"This is a string.";

They don't realize the @ returns an NSString literal, so trying to mutate the string will raise an immutability exception. You could use -initWithString to create the mutable string or even get a mutable copy from the literal since it's actually an NSString object:

NSMutableString *aString = [@"This is a string." mutableCopy];

Without garbage collection, you'll have to release that object since you gain ownership with the copy message.

Preston
The compiler emits a warning for assigning an incompatible pointer type (even with a default project).
Nikolai Ruhe
Nikolai:GCC 4.0, the default compiler on Leopard, doesn't warn for this.
Preston
A: 

Just for the record, to remove all subviews, you can do the following:

myUIView.subviews = [NSArray array];
damacster
NSView does not have a `subviews` property and `UIView`’s is readonly.
Nikolai Ruhe
... but `NSView`’s `setSubviews:` method might work.
Nikolai Ruhe
+4  A: 

I'm a newbie here so I can't comment on an existing answer apparently, but the example above:

NSMutableString *aString = [@"This is a string." mutableCopy];

is wrong: it's a memory leak. It should be:

NSMutableString *aString = [[@"This is a string." mutableCopy] autorelease];

DavidPhillipOster
I've edited that answer to include the autorelease. Thanks for pointing it out.
Peter Hosey
... and I’m giving you credit for it to get you one step closer to comments.
Nikolai Ruhe
I personally prefer: `NSMutableString *aString = [NSMutableString stringWithString:@"This is a string."];`
Dave DeLong
+3  A: 

Categorying methods onto Apple classes. In theory, a convenient way to add features beyond what Apple provides. In reality, if Apple later adds the same method under the same name, you may break it. One form of breakage is if something else in Cocoa relies on a side effect of the method and your implementation doesn't have the same side effect; another is if Apple calls the method on multiple threads and your implementation isn't thread-safe.

There are three right ways to add functionality to a class:

  1. Implement the methods in a subclass (not always feasible).
  2. Add the method to the original class, but add a distinguishing feature that Apple is unlikely to copy to the selector, such as your initials.
  3. Implement the methods as C functions instead.

Safe uses for categories include:

  • Informal protocols (now made obsolete by the @optional keyword in formal protocols)
  • Grouping methods into actual categories
Peter Hosey
+1 Name clashes are a very good point—even though not only in Objective-C. Extending classes by use of categories is an Objective-C thing but I don’t see much difference to extending classed by inheritance. The likeliness to get a name clash is the same when adding new methods to a subclass. The only difference is that all instances of that class will suffer from the name clash, not only those instantiated from your code.
Nikolai Ruhe
The difference is that if you clobber one of Apple's methods in one of their classes, their own messages to their instances of their classes will all go to your implementation. Most Apple APIs don't create instances of your subclasses, so your subclasses wouldn't have that problem.
Peter Hosey
One of my subclasses **did have** the same problem: In one iPhone project I had a subclass of UITableViewCell. I added a `highlighted` property to this subclass. Then iPhone OS 3.0 came out and added a property with the same name to `UITableViewCell`. All of my cells suddenly went crazy on updated devices. While it’s nice that Apple’s own `UITableViewCell`s did not have the problem it didn’t matter in this project, since there were none. What I’m saying is that this is a general problem of OOP, not only Objective-C’s categories.
Nikolai Ruhe
Yeah, I'm talking about cases where an Apple class instantiates *an Apple class* and sends a message to the instance (or, an Apple class sends a message to *another Apple class*), and ends up in a non-Apple method implementation. That's only possible with categories; the case that you had can happen any time one of Apple's objects sends a message to one of yours (and, as you note, it is not limited to Objective-C). Not categorying onto Apple classes simply removes one possible cause of the problem.
Peter Hosey
Yeah, and I’m just saying that adding a category and subclassing are both ways to get name clashes in future OS versions. Categories already have a reputation of breaking things when used on base classes. I’ve added the comment to emphasize the fact that the problem does also exist when inheriting (which, in my perception, is less talked about).
Nikolai Ruhe
+6  A: 

Retaining a delegate.

And related:

Assuming you will get retained if you pass yourself as a delegate to another object.

I've seen folks often screw up the delegate rules quite badly. For the most part it is simple. Delegates are not retained. This is to prevent retain cycles.

Exceptions exist of course: NSURLConnection will retain its delegates (so the solution is to explicitly break the retain cycle when the URL connection finishes or fails).

schwa
+2  A: 

Assuming your project will make a "build" directory next to your .xcodeproj file.

Some people prefer to have customized & centralized build locations. If you must refer to something inside a build directory (for custom build actions etc) use something like: $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

These custom environment variables are documented in xcode's docs.

schwa
+2  A: 

Class clusters break the isKindOfClass: expected behaviour. This is documented in the NSObject Protocol Reference:

Be careful when using this method on objects represented by a class cluster. Because of the nature of class clusters, the object you get back may not always be the type you expected. If you call a method that returns a class cluster, the exact type returned by the method is the best indicator of what you can do with that object. For example, if a method returns a pointer to an NSArray object, you should not use this method to see if the array is mutable.

This was thoroughly discussed in 2004 on the MacOSX-dev mailing list mutability? thread and it's still very true.

0xced
+1 Excelent point. I was not aware of this specific Foundation weirdness.
Nikolai Ruhe