I know about the HIG (which is quite handy!), but what programming practices do you use when writing Objective-C, and more specifically when using Cocoa (or CocoaTouch).
views:
22397answers:
30There are a few things I have started to do that I do not think are standard:
1) With the advent of properties, I no longer use "_" to prefix "private" class variables. After all, if a variable can be accessed by other classes shouldn't there be a property for it? I always disliked the "_" prefix for making code uglier, and now I can leave it out.
2) Speaking of private things, I prefer to place private method definitions within the .m file in a private category like so:
#import "MyClass.h"
@interface MyClass ()
- (void) someMethod
- (void) someOtherMethod
@end
@implementation MyClass
Why clutter up the .h file with things outsiders should not care about? The empty () works for private categories in the .m file, and issues compile warnings if you do not implement the methods declared.
3) I have taken to putting dealloc at the top of the .m file, just below the @synthesize directives. Shouldn't what you dealloc be at the top of the list of things you want to think about in a class? That is especially true in an environment like the iPhone.
3.5) In table cells, make every element (including the cell itself) opaque for performance. That means setting the appropriate background color in everything.
3.6) When using an NSURLConnection, as a rule you may well want to implement the delegate method:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
I find most web calls are very singular and it's more the exception than the rule you'll be wanting responses cached, especially for web service calls. Implementing the method as shown disables caching of responses.
Also of interest, are some good iPhone specific tips from Joseph Mattiello (received in an iPhone mailing list). There are more, but these were the most generally useful I thought (note that a few bits have now been slightly edited from the original to include details offered in responses):
4) Avoid doubles! The iPhone DOES NOT support ANY double-precision calculation natively. These are also emulated using libraries. Only use double precision if you have to, CoreLocation for instance. Make sure you end your constants in 'f' to make gcc store them as floats.
float val = someFloat * 2.2f;
This is mostly important when someFloat
may acually be a double, you don't need the mixed-mode math, since you're losing precision in 'val' on storage.
EDIT: Things have changed slightly with the 3GS, even more reason to use single precision when possible:
http://stackoverflow.com/questions/1622729/double-vs-float-on-the-iphone
On the older phones supposedly calculations operate at the same speed but you can have more single precision components in registers than doubles, so for many calculations single precision will end up being faster.
5) Set your properties as nonatomic
. They're atomic
by default and upon synthesis, semaphore code will be created to prevent multi-threading problems. 99% of you probably don't need to worry about this and the code is much less bloated and memory efficient when set to nonatomic.
6) SQLite can be a very, very fast way to cache large data sets. A map application for instance can cache it's tiles into SQLite files. The most expensive part is disk I/O. Avoid many small writes by sending BEGIN;
and COMMIT;
between large blocks. We use a 2 second timer for instance that resets on each new submit. When it expires, we send COMMIT; , which causes all your writes to go in one large chunk. SQLLite stores transaction data to disk and doing this Begin/End wrapping avoids creation of many transaction files, grouping all of the transactions into one file.
Also, SQL will block your GUI if it's on your main thread. If you have a very long query, It's a good idea to store your queries as static objects, and run your SQL on a separate thread. Make sure to wrap anything that modifies the database for query strings in @synchronize() {}
blocks. For short queries just leave things on the main thread for easier convenience.
More SQLLite optimization tips are here, though the document appears out of date many of the points are probably still good;
http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
@kendell
Instead of:
@interface MyClass (private)
- (void) someMethod
- (void) someOtherMethod
@end
Use:
@interface MyClass ()
- (void) someMethod
- (void) someOtherMethod
@end
New in Objective-C 2.0.
Class extensions are described in Apple's Objc 2 Reference.
"Class extensions allow you to declare additional required API for a class in locations other than within the primary class @interface block"
So they're part of the actual class - and NOT a (private) category in addition to the class. Subtle but important difference.
Clean up in dealloc.
This is one of the easiest things to forget - esp. when coding at 150mph. Always, always, always clean up your attributes/member variables in dealloc.
I like to use Objc 2 attributes - with the new dot notation - so this makes the cleanup painless. Often as simple as:
- (void)dealloc
{
self.someAttribute = NULL;
[super dealloc];
}
This will take care of the release for you and set the attribute to NULL (which I consider defensive programming - in case another method further down in dealloc accesses the member variable again - rare but could happen).
With GC turned on in 10.5, this isn't needed so much any more - but you might still need to clean up others resources you create, you can do that in the finalize method instead.
This is subtle one but handy one. If you're passing yourself as a delegate to another object, reset that object's delegate before you dealloc.
- (void)dealloc
{
self.someObject.delegate = NULL;
self.someObject = NULL;
//
[super dealloc];
}
By doing this you're ensuring that no more delegate methods will get sent. As you're about to dealloc and disappear into the ether you want to make sure that nothing can send you any more messages by accident. Remember self.someObject could be retained by another object (it could be a singleton or on the autorelease pool or whatever) and until you tell it "stop sending me messages!", it thinks your just-about-to-be-dealloced object is fair game.
Getting into this habit will save you from lots of weird crashes that are a pain to debug.
The same principal applies to Key Value Observation, and NSNotifications too.
Edit:
Even more defensive, change:
self.someObject.delegate = NULL;
into:
if (self.someObject.delegate == self)
self.someObject.delegate = NULL;
Try to avoid what I have now decided to call Newbiecategoryaholism. When newcomers to Objective-C discover categories they often go hog wild, adding useful little categories to every class in existence ("What? i can add a method to convert a number to roman numerals to NSNumber rock on!").
Don't do this.
Your code will be more portable and easier to understand with out dozens of little category methods sprinkled on top of two dozen foundation classes.
Most of the time when you really think you need a category method to help streamline some code you'll find you never end up reusing the method.
There are other dangers too, unless you're namespacing your category methods (and who besides the utterly insane ddribin is?) there is a chance that Apple, or a plugin, or something else running in your address apce will also define the same category method with the same name with a slightly different side effect....
OK. Now that you've been warned, ignore the "don't do this part". But exercise extreme restraint.
Also, semi-related topic (with room for more responses!):
What are those little Xcode tips & tricks you wish you knew about 2 years ago?.
Write unit tests. You can test a lot of things in Cocoa that might be harder in other frameworks. For example, with UI code, you can generally verify that things are connected as they should be and trust that they'll work when used. And you can set up state & invoke delegate methods easily to test them.
You also don't have public vs. protected vs. private method visibility getting in the way of writing tests for your internals.
Resist subclassing the world. In Cocoa a lot is done through delegation and use of the underlying runtime that in other frameworks is done through subclassing.
For example, in Java you use instances of anonymous *Listener
subclasses a lot and in .NET you use your EventArgs
subclasses a lot. In Cocoa, you don't do either — the target-action is used instead.
Don't write Objective-C as if it were Java/C#/C++/etc.
I once saw a team used to writing J2EE web applications try to write a Cocoa desktop application. As if it was a J2EE web application. There was a lot of AbstractFooFactory and FooFactory and IFoo and Foo flying around when all they really needed was a Foo class and possibly a Fooable interface.
Part of ensuring you don't do this is truly understanding the differences in the language. For example, you don't need the abstract factory and factory classes above because Objective-C class methods are dispatched just as dynamically as instance methods, and can be overridden in subclasses.
Use standard Cocoa naming and formatting conventions and terminology rather than whatever you're used to from another environment. There are lots of Cocoa developers out there, and when another one of them starts working with your code, it'll be much more approachable if it looks and feels similar to other Cocoa code.
Examples of what to do and what not to do:
- Don't declare
id m_something;
in an object's interface and call it a member variable or field; usesomething
or_something
for its name and call it an instance variable. - Don't name a getter
-getSomething
; the proper Cocoa name is just-something
. - Don't name a setter
-something:
; it should be-setSomething:
- The method name is interspersed with the arguments and includes colons; it's
-[NSObject performSelector:withObject:]
, notNSObject::performSelector
. - Use inter-caps (CamelCase) in method names, parameters, variables, class names, etc. rather than underbars (underscores).
- Class names start with an upper-case letter, variable and method names with lower-case.
Whatever else you do, don't use Win16/Win32-style Hungarian notation. Even Microsoft gave up on that with the move to the .NET platform.
I know I overlooked this when first getting into Cocoa programming.
Make sure you understand memory management responsibilities regarding NIB files. You are responsible for releasing the top-level objects in any NIB file you load. Read Apple's Documentation on the subject.
Make sure you bookmark the Debugging Magic page. This should be your first stop when banging your head against a wall while trying to find the source of a Cocoa bug.
For example, it will tell you how to find the method where you first allocated memory that later is causing crashes (like during app termination).
If you're using Leopard (Mac OS X 10.5) or later, you can use the Instruments application to find and track memory leaks. After building your program in Xcode, select Run > Start with Performance Tool > Leaks.
Even if your app doesn't show any leaks, you may be keeping objects around too long. In Instruments, you can use the ObjectAlloc instrument for this. Select the ObjectAlloc instrument in your Instruments document, and bring up the instrument's detail (if it isn't already showing) by choosing View > Detail (it should have a check mark next to it). Under "Allocation Lifespan" in the ObjectAlloc detail, make sure you choose the radio button next to "Created & Still Living".
Now whenever you stop recording your application, selecting the ObjectAlloc tool will show you how many references there are to each still-living object in your application in the "# Net" column. Make sure you not only look at your own classes, but also the classes of your NIB files' top-level objects. For example, if you have no windows on the screen, and you see references to a still-living NSWindow, you may have not released it in your code.
Don't forget that NSWindowController and NSViewController will release the top-level objects of the NIB files they govern.
If you manually load a NIB file, you are responsible for releasing that NIB's top-level objects when you are done with them.
IBOutlets
Historically, memory management of outlets has been poor. Current best practice is to declare outlets as properties:
@interface MyClass :NSObject {
NSTextField *textField;
}
@property (nonatomic, retain) IBOutlet NSTextField *textField;
@end
Using properties makes the memory management semantics clear; it also provides a consistent pattern if you use instance variable synthesis.
Declared Properties
You should typically use the Objective-C 2 Declared Properties feature for all your properties. If they are not public, add them in a class extension. Using declared properties makes the memory management semantics immediately clear, and makes it easier for you to check your dealloc method -- if you group your property declarations together you can quickly scan them and compare with the implementation of your dealloc method.
You should think hard before not marking properties as 'nonatomic'. As The Objective C Programming Language Guide notes, properties are atomic by default, and incur considerable overhead. Moreover, simply making all your properties atomic does not make your application thread-safe. Also note, of course, that if you don't specify 'nonatomic' and implement your own accessor methods (rather than synthesising them), you must implement them in an atomic fashion.
Use the LLVM/Clang Static Analyzer
You use the Clang Static Analyzer to -- unsurprisingly -- analyse your C and Objective-C code (no C++ yet) on Mac OS X 10.5. It's trivial to install and use:
- Download the latest version from this page.
- From the command-line,
cd
to your project directory. - Execute
scan-build -k -V xcodebuild
.
(There are some additional constraints etc., in particular you should analyze a project in its "Debug" configuration -- see http://clang.llvm.org/StaticAnalysisUsage.html for details -- the but that's more-or-less what it boils down to.)
The analyser then produces a set of web pages for you that shows likely memory management and other basic problems that the compiler is unable to detect.
Don't use unknown strings as format strings
When methods or functions take a format string argument, you should make sure that you have control over the content of the format string.
For example, when logging strings, it is tempting to pass the string variable as the sole argument to NSLog
:
NSString *aString = // get a string from somewhere;
NSLog(aString);
The problem with this is that the string may contain characters that are interpreted as format strings. This can lead to erroneous output, crashes, and security problems. Instead, you should substitute the string variable into a format string:
NSLog(@"aString: %@", aString);
Sort strings as the user wants
When you sort strings to present to the user, you should not use the simple compare:
method. Instead, you should always use localized comparison methods such as localizedCompare:
or localizedCaseInsensitiveCompare:
.
For more details, see Searching, Comparing, and Sorting Strings.
Avoid autorelease
Since you typically(1) don't have direct control over their lifetime, autoreleased objects can persist for a comparatively long time and unnecessarily increase the memory footprint of your application. Whilst on the desktop this may be of little consequence, on more constrained platforms this can be a significant issue. On all platforms, therefore, and especially on more constrained platforms, it is considered best practice to avoid using methods that would lead to autoreleased objects and instead you are encouraged to use the alloc/init pattern.
Thus, rather than:
aVariable = [AClass convenienceMethod];
where able, you should instead use:
aVariable = [[AClass alloc] init];
// do things with aVariable
[aVariable release];
When you're writing your own methods that return a newly-created object, you can take advantage of Cocoa's naming convention to flag to the receiver that it must be released by prepending the method name with "new".
Thus, instead of:
- (MyClass *)convenienceMethod {
MyClass *instance = [[[self alloc] init] autorelease];
// configure instance
return instance;
}
you could write:
- (MyClass *)newInstance {
MyClass *instance = [[self alloc] init];
// configure instance
return instance;
}
Since the method name begins with "new", consumers of your API know that they're responsible for releasing the received object (see, for example, NSObjectController's newObject
method).
(1) You can take control by using your own local autorelease pools. For more on this, see Autorelease Pools.
Think about nil values
As this question notes, messages to nil
are valid in Objective-C. Whilst this is frequently an advantage -- leading to cleaner and more natural code -- the feature can occasionally lead to peculiar and difficult-to-track-down bugs if you get a nil
value when you weren't expecting it.
Some of these have already been mentioned, but here's what I can think of off the top of my head:
- Follow KVO naming rules. Even if you don't use KVO now, in my experience often times it's still beneficial in the future. And if you are using KVO or bindings, you need to know things are going work the way they are supposed to. This covers not just accessor methods and instance variables, but to-many relationships, validation, auto-notifying dependent keys, and so on.
- Put private methods in a category. Not just the interface, but the implementation as well. It's good to have some distance conceptually between private and non-private methods. I include everything in my .m file.
- Put background thread methods in a category. Same as above. I've found it's good to keep a clear conceptual barrier when you're thinking about what's on the main thread and what's not.
- Use
#pragma mark [section]
. Usually I group by my own methods, each subclass's overrides, and any information or formal protocols. This makes it a lot easier to jump to exactly what I'm looking for. On the same topic, group similar methods (like a table view's delegate methods) together, don't just stick them anywhere. - Prefix private methods & ivars with _. I like the way it looks, and I'm less likely to use an ivar when I mean a property by accident.
- Don't use mutator methods / properties in init & dealloc. I've never had anything bad happen because of it, but I can see the logic if you change the method to do something that depends on the state of your object.
- Put IBOutlets in properties. I actually just read this one here, but I'm going to start doing it. Regardless of any memory benefits, it seems better stylistically (at least to me).
- Avoid writing code you don't absolutely need. This really covers a lot of things, like making ivars when a
#define
will do, or caching an array instead of sorting it each time the data is needed. There's a lot I could say about this, but the bottom line is don't write code until you need it, or the profiler tells you to. It makes things a lot easier to maintain in the long run. - Finish what you start. Having a lot of half-finished, buggy code is the fastest way to kill a project dead. If you need a stub method that's fine, just indicate it by putting
NSLog( @"stub" )
inside, or however you want to keep track of things.
All these comments are great, but I'm really surprised nobody mentioned Google's Objective-C Style Guide that was published a while back. I think they have done a very thorough job.
Use NSAssert and friends. I use nil as valid object all the time ... especially sending messages to nil is perfectly valid in Obj-C. However if I really want to make sure about the state of a variable, I use NSAssert and NSParameterAssert, which helps to track down problems easily.
One of the answers here states that "the most expensive part [of SQLite] is disk I/O." Normally, this is true, but on the iPhone, there is no disk. That makes the SQLite database, while not quite as fast as direct memory access, a lot faster than a disk based SQLite. Having a RAM based SQL database is a wonderful asset for persistant data - not only do you get near instant access to your data, but you can use it to push big ticket data out of your application's memory space and bring it back in really fast.
The Apple-provided samples I saw treated the App delegate as a global data store, a data manager of sorts. That's wrongheaded. Create a singleton and maybe instantiate it in the App delegate, but stay away from using the App delegate as anything more than application-level event handling. I heartily second the recommendations in this blog entry. This thread tipped me off.
Turn on all GCC warnings, then turn off those that are regularly caused by Apple's headers to reduce noise.
Also run Clang static analysis frequently; you can enable it for all builds via the "Run Static Analyzer" build setting.
Write unit tests and run them with each build.
One rather obvious one for a beginner to use: utilize XCode's auto-indentation feature for your code. Even if you are copy/pasting from another source, once you have pasted the code, you can select the entire block of code, right click on it, and then choose the option to re-indent everything within that block.
XCode will actually parse through that section and indent it based on brackets, loops, etc. It's a lot more efficient than hitting the space bar or tab key for each and every line.
Simple but oft-forgotten one. According to spec:
In general, methods in different classes that have the same selector (the same name) must also share the same return and argument types. This constraint is imposed by the compiler to allow dynamic binding.
in which case all the same named selectors, even if in different classes, will be regarded as to have identical return/argument types. Here is a simple example.
@interface FooInt:NSObject{}
-(int) print;
@end
@implementation FooInt
-(int) print{
return 5;
}
@end
@interface FooFloat:NSObject{}
-(float) print;
@end
@implementation FooFloat
-(float) print{
return 3.3;
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id f1=[[FooFloat alloc]init];
//prints 0, runtime considers [f1 print] to return int, as f1's type is "id" and FooInt precedes FooBar
NSLog(@"%f",[f1 print]);
FooFloat* f2=[[FooFloat alloc]init];
//prints 3.3 expectedly as the static type is FooFloat
NSLog(@"%f",[f2 print]);
[f1 release];
[f2 release]
[pool drain];
return 0;
}