What are some common mistakes made by Objective-C developers, and how can we avoid them? I would really like to see common memory management mistakes, anti-patterns or any other mistakes on iPhone and OSX platforms as well.
Please justify your answer as well, if applicable and give examples.
If on the other hand you have best practices that is welcome too - it's just the opposite of my question so doing it wrong might answer my question :) ...
retainCount
can be valuable in debugging (e.g., a category that logs -retain and -release) as long as one understands that objects get retained and released all over the place. - Justin Spahr-Summers
retainCount
is never a useful debugging aid. There are always better means of analyzing memory use, retain/release problems, over-release issues, crashes, and egregious abuse of memory. Use of retainCount
is several steps below printf()
in the spectrum of debugging tools; only to be used out of abject desperation. - bbum
retainCount
can be useful for finding leaks, since the Leaks instrument is notably lame. I mentioned in my last comment a category that logs retain
and release
– applying such a category to UIViewController
, for example, can help track down leaky controllers in a way that Leaks really can't. - Justin Spahr-Summers
retainCount
is not useful for finding leaks. Not in comparison to either the Allocations instrument or Heapshot analysis. Making such a claim is like claiming that a spoon is a better choide building a swimming pool than a bulldozer when you have the keys to the bulldozer in your hand. Leaks isn't, and never has been, used to track down allocations that have outlived their usefulness; a leak is an object that is no longer referenced; a very different problem. - bbum
retain/release
as a convenient means of breaking/logging is useful, but the value returned by retainCount
is not. - bbum
retain
and release
gets extremely messy, and printing out the retain count (as well as the object being retained or released) is the only way to understand the sequence of what's happening. - Justin Spahr-Summers
retainCount
and asking (a) what problems might lurk within said use and (b) if there is a better way. - bbum
Great question! A sporadic list of things I've noticed;
Finally, make sure to use the tools that Apple provides. Once you perform a Build to test for syntax errors, also run a Build and Analyze (⌘⇧A)—it'll run the static analyzer, which will detect basic errors that you might miss, such as memory leaks, an if without an else, which may end up returning a garbage value because you don't catch every circumstance, and some subtle things like that. And of course, test your program—press every single button, with all different types of inputs. Instead of just putting in your name where it asks for your name, try putting in a period. Or a comma. Or a negative number. See what your program does. Do everything you possibly can to break your code. After all, that's what your customer will (inadvertently or not) be trying to do.
Happy coding!
[1] http://cocoawithlove.com/2009/06/method-names-in-objective-c.html.
and =
operators. - dreamlax
A lot of developers coming from Java and C# confuse properties with the instance variables that provide their storage. This results in complaints like "Why do I have to type my members three times?" when what they're doing each time is quite different. Furthermore, a lot of such developers wind up exposing a lot more state to clients of a class than they need to, because they think they need properties everywhere.
For example, they are used to accessing instance variables by dereferencing this
, e.g. this.foo = bar;
. One should not blindly follow this pattern in Objective-C. When you say self.foo = bar;
what you are doing is invoking the -setFoo:
method, not simply changing the foo
instance variable.
A common idiom among experienced Objective-C developers is to name instance variables with a prefix or suffix different from any associated property. This makes it very clear to readers of code when you're working with an instance variable compared to a property (or even a local variable). For example:
@interface TimelineViewController : NSViewController {
@private
NSCollectionView *_timelineView;
}
@property (readwrite, retain) IBOutlet NSCollectionView *timelineView;
@end
@implementation TimelineViewController
@synthesize timelineView = _timelineView; // associate property with ivar
- (void)dealloc {
[_timelineView release]; // use ivar, not property, in -dealloc
[super dealloc];
}
@end
nil
values, especially overridden ones. Also, setting properties to nil
in dealloc
means that the code remains valid no matter what the memory management semantics are. Using naked ivars makes inheritance significantly more difficult, as subclasses must try to catch every corner case where an ivar was referenced instead of a property. - Justin Spahr-Summers
init
is well-taken, but note that Apple is considerably less strict about dealloc
than you make it sound: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/… - Justin Spahr-Summers
One issue I see with developers coming from other environments is use of incorrect terminology. As a Smalltalk derivative, Objective-C adheres pretty closely to the traditional terminology of object-oriented programming as defined by Smalltalk, in addition to regular C terminology:
There are also a few other oddities I've seen in the terms used by some people coming to Objective-C recently:
You can't compare NSString
's with ==
. This is one of the most common mistakes I've seen on SO.
Don't do this:
if (helloString == @"Hello") {
}
Do this:
if ([helloString isEqualToString:@"Hello"]) {
}
Based on the questions I've seen here and on Apple's developer forums:
I think that the most valuable best practice I can suggest would be to learn to write tests. Setting up a testing framework in Xcode is harder than it should be but there are some good alternatives to OCUnit and I think the "if you can't write a test for it then you don't understand it" aspect of TDD would catch most of the mistakes I've seen.
Another thing developers coming to Objective-C need to do:
Mind your history!
Objective-C is a derivative of C and Smalltalk; most of its concepts come from one or the other. It's not a language developed in complete isolation by Apple for the express purpose of iPhone development, so don't act like it is when discussing iPhone development.
In fact, if you've been working in Java (or in a Java clone like C#) most of what you've been using was explicitly derived by its creators from the features of Objective-C. There's a lot more and richer history to the language, the tools, and the platform than many developers new to it seem to give it credit for.
Another common mistake for developers used to other languages: Declaring a new class without a superclass, and assuming it will have a default superclass.
Objective-C uses single-inheritance but does support multiple root classes. Thus if you write
/* has no superclass, don't do this*/
@interface MyClass
@end
you are actually declaring a new root class rather than a subclass of NSObject.
All subclasses of NSObject must declare themselves as such, like so:
/* superclass is NSObject */
@interface MyClass : NSObject
@end
If you don't do this, the compiler will warn you that your class doesn't implement certain required methods such as -methodSignatureForSelector:
and -forwardInvocation:
.
NSObject
and NSProxy
, but yes, this is good advice. - Jacob Relkin
Mistakes dealing with memory management are probably the most common, especially for former C/C++ programmers. Dereferencing object pointers, instead of using properties comes in at a close second.
I think that there is initially much confusion about
object ownership
[1] and why it truly matters if you name your method +newObject
versus +object
.
BOOL
is not a “real“ boolean type, it is a typedef for a signed char, so don't compare a BOOL
variable directly to YES
, instead just rely on whether it is zero or non-zero:
BOOL flag = /* obtain from somewhere */;
if (flag)
{
}
Some places are guaranteed to return YES
rather than just a non-zero value, for example
[NSNumber boolValue]
[1] as of Mac OS X 10.3, but not everywhere will do the same.
bool
from Stdbool.h
. - Kendall Hopkins
bool
macro in stdbool.h
expands into _Bool
, which is an integer type that can only be either 0, or 1 (when non-zero scalar values are converted to _Bool
type, they are converted to 1, otherwise they are converted to 0). The true
macro expands to 1
and the false
macro expands to 0
. It is perfectly safe (disregarding further redefinitions of the macros) to compare bool
types to true
or false
. - dreamlax
_Bool
type, and 7.16 for the standard definitions of bool
, true
and false
in stdbool.h
. - dreamlax
When subclassing an Apple framework class, avoid using the underscore prefix (Apple reserves it).
Avoid the use of the underscore character as a prefix meaning private, especially in methods. Apple reserves the use of this convention. Use by third parties could result in name-space collisions; they might unwittingly override an existing private method with one of their own, with disastrous consequences. See “Private Methods” [1] for suggestions on conventions to follow for private API.
(emphasis mine)
See: Coding Guidelines for Cocoa – Typographic Conventions [2]
[1] http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html#//apple_ref/doc/uid/20001282-1003829You can't check the value of an int like this:
[person setName:@"Charlie"];
[person setAge:23];
NSLog(@"Name: %@ Age: %@", [person name], [person age]);
Forgetting this has caused me many a crash. Here's how to do it:
NSLog(@"Name: %@ Age: %d", [person name], [person age]);
%i
and %d
do exactly the same. %i
stands for integer (against %f
for float), %d
stands for decimal (against %x
for hexadecimal). I would use %i
in this context but it makes no real difference. - Sulthan
OK, nobody has put this one explicitly, although several have alluded to it:
failure to understand and follow the Memory Management Rules [1]. Too many Stack Overflow questions go along the lines of:
I do this:
myIvar = [foo bar];
and it crashes, or I do this:
[self setMyIvar: [[Foo alloc] init]];
and it leaks.
Also, to reiterate Jonah's sixth point, you see this surprisingly often:
myArray = [[NSMutableArray alloc] init];
myArray = [someOtherArray mutableCopy];
along with the question "where is my leak?"
[1] http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/MemoryMgmt/Articles/mmRules.htmlA really common error for beginners is to access fields of a struct belong to a property with the dot notation.
A common mistake I've been noticing lately is folks who declare properties and then make decidedly non-property-like implementations, rather than using @synthesize and using KVO to ripple out any desired side effects. Apple docs [1] tout the following as a "feature" of declared properties:
The property declaration provides a clear, explicit specification of how the accessor methods behave.
The specification implied by the declaration is only as accurate as the implementation is faithful. For instance, if you have a property like this:
@property (nonatomic, copy, readwrite) NSString* dateString;
And then you implement it like this:
@synthesize dateString;
- (NSString*)dateString
{
if (dateString == nil)
{
return [[NSDate date] description];
}
return dateString;
}
You've broken the implied commitment that this is a thin, storage-only property with nonatomic, copy, readwrite characteristics. This may not be a big deal when working within your own products, but if you're writing class libraries for others to consume, the declaration may be all consumers have to go on, yet there's this hidden behavior.
Also dangerous is overriding property accessors/mutators in subclasses. Sometimes this is unavoidable, but it's important to try and stay true to the promises made by the initial declaration, since those are the promises that others will have in mind when they polymorphically consume your subclass instance.
[1] http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocProperties.htmlJust because you release an object, doesn't mean it goes away. It just means that code won't use it again. It can still be retained by other code you passed it to, the system in a cache or shared object, or the runloop. This is why you need to tell an object you are no longer its delegate before releasing it, or you will/may crash.