University of Utah CS 4962 Spring 2012 Objective-C
2.0 Style Guide
Code
that uses a consistent style is less prone to errors, easier to read, and
easier to maintain. Using consistent style also helps to promote complete
coding techniques, giving a method, class, or subsystem a feeling that it was
well designed. To this end, we have created a style guide for students of CS 4962. It is based on a style guide used at Pixio Software, which style guide was adapted from a style guide published by Google Inc.
Code Example
//
// PEPolygon.h
// PEPolygon
//
// Created by
Student on 11/11/11.
// Copyright
2009 Polygon. All rights reserved.
//
#import <Foundation/Foundation.h>
@class PEPolygon;
/**
PEPolygonDelegate is the delegate protocol for a PEPolygon. */
@protocol PEPolygonDelegate
/**
Called when the connection successfully authenticates
@param connection Connection
object sending this message */
-
(void) polygon:(PEPolygon*)polygon sideCountChanged:(int)sideCount;
@end
/**
PEPolygon is an abstract representation of a
classical polygon. */
@interface PEPolygon : NSObject
{
NSObject<PEPolygonDelegate>* _delegate;
NSInteger _sideCount;
NSString* _name;
}
/**
Initializes a polygon with a name and side count
@param sideCount Number of
sides in the polygon
@param name String specifying the name of the polygon
*/
-
(id) initWithSideCount:(NSInteger)sideCount name:(NSString*)name;
/**
Delegate that receives notifications of changes in the polygon's state */
@property (assign) NSObject<PEPolygonDelegate>* delegate;
/**
Number of sides the polygon has */
@property (assign) NSInteger
sideCount;
/**
Name of the polygon */
@property (copy) NSString*
name;
/**
Draws the polygon using the specified CGContextRef
object */
-
(void) drawUsingContext:(CGContextRef)context;
@end
//
// PEPolygon.m
// PEPolygon
//
// Created by
Student on 11/11/11.
// Copyright
2009 Polygon. All rights reserved.
//
#import "PEPolygon.h"
#define PEPolygonMinimumSideCount 3
#define PEPolygonDefaultSideCount PEPolygonMinimumSideCount
#define PEPolygonDefaultName @"Triangle"
#pragma mark -
#pragma mark Private Interface
@interface PEPolygon () <PEShapeProtocol>
-
(void) verifyAnglesUsingRadians:(BOOL)usingRadians;
@end
#pragma mark -
@implementation PEPolygon
#pragma mark -
#pragma mark Constructors
-
(id) initWithSideCount:(int)passedSideCount
name:(NSString*)passedName
{
self = [super init];
if (self == nil)
return nil;
// Initialize each data
member of the class.
// DonÕt reinitialize
members to 0 or nil as it is redundant.
self.sideCount = passedSideCount;
self.name = passedName;
return self;
}
-
(id) init
{
return [self initWithSideCount:PEPolygonDefaultSideCount
name:PEPolygonDefaultName];
}
-
(void) dealloc
{
// Always disassociate any
delegate objects
self.delegate = nil;
// Release all Objective-C
objects
[_name
release];
// The only time to ever
call dealloc is on the super class
[super dealloc];
}
#pragma mark -
#pragma mark Accessors
@synthesize delegate = _delegate;
@synthesize sideCount = _sideCount;
-
(void) setSideCount:(NSInteger)passedSideCount
{
// Verify input data to accessors
if (passedSideCount
< PEPolygonMinimumSideCount)
sideCount = PEPolygonMinimumSideCount;
else
sideCount = passedSideCount;
// When making calls to a
delegate, ensure that the delegate is not nil and
// responds
to the selector
if (self.delegate != nil &&
[self.delegate respondsToSelector:@selector(polygon:sideCountChanged:)])
{
[self.delegate polygon:self sideCountChanged:sideCount];
}
}
@synthesize name = _name;
#pragma mark -
#pragma mark Methods
-
(void) drawUsingContext:(CGContextRef)context
{
// Code omitted
}
-
(void) verifyAnglesUsingRadians:(BOOL)usingRadians
{
// Code omitted
}
#pragma mark -
#pragma mark PEShapeProtocol
Methods
-
(void) formalizeShape
{
// Code omitted
}
@end
Spacing and Formatting
Use
only spaces, and indent 4 spaces at a time.
We
use spaces for indentation. Do not use tabs in your code. You should set your
editor to emit spaces when you hit the tab key.
Line Length
Each
line of text in your code should be at
most 132 characters long.
You
can make violations easier to spot in Xcode by going
to Xcode > Preferences > Text Editing >
Show page guide.
Method Declarations and
Definitions
One
space should be used between the - or + and the return type, and no spacing in
the parameter list except between parameters.
Methods
should look like this:
- (void) doSomethingWithString:(NSString*)string
{
...
}
If
you have too many parameters to fit on one line, giving each its own line is
preferred. If multiple lines are used, insert 4 spaces before the parameter.
- (void) doSomethingWith:(GTFoo*)foo
rect:(NSRect)rect
interval:(float)interval
{
...
}
Naming
Naming
rules are very important in maintainable code. Objective-C method names tend to
be very long, but this has the benefit that a block of code can almost read
like prose, thus rendering many comments unnecessary.
When
writing pure Objective-C code, we mostly follow standard Objective-C naming
rules. This guide recommends the use of intercaps,
which is standard in the Objective-C community.
When
writing Objective-C++, however, things are not so cut and dry. Many projects
need to implement cross-platform C++ APIs with some Objective-C or Cocoa, or
bridge between a C++ back-end and a native Cocoa front-end. This leads to
situations where the two guides are directly at odds.
Our
solution is that the style follows that of the method/function being
implemented. If you're in an @implementation block, use the Objective-C naming
rules. If you're implementing a method for a C++ class, use the C++ naming
rules. This avoids the situation where instance variable and local variable
naming rules are mixed within a single function, which would be a serious
detriment to readability.
File Names
File
names should reflect the name of the class implementation that they contain --
including case. Follow the convention that your project uses.
File
extensions should be as follows:
.h
C/C++/Objective-C
header file
.m
Objective-C
implementation file
.mm
Objective-C++
implementation file
.cpp Pure
C++ implementation file
.c
C
implementation file
File
names for categories should include the name of the class being extended, e.g. GTNSString+Utils.h or GTNSTextView+Autocomplete.h
Class Names
Class
names (along with category and protocol names) should start as uppercase and
use mixed case to delimit words. Use of a 2 character naming
prefix for all classes is strongly recommended as Objective-C has no namespace
control structures, e.g. PEPolygon
Category Names
Category
names should start with a 2 character prefix
identifying the category as part of a project or open for general use. The
category name should incorporate the name of the class it's extending. e.g. GTStringParsingAdditions
Objective-C Method Names
Method
names should start as lowercase and then use mixed case. Each named parameter
should also start as lowercase.
The
method name should read like a sentence if possible, meaning you should choose
parameter names that flow with the method name. (e.g. convertPoint:fromRect: or replaceCharactersInRange:withString:).
See Apple's Guide to Naming Methods for more details.
Accessor methods should be named the same as the variable they're
"getting", but they should not be prefixed with the word
"get". For example:
- (id) getDelegate; // AVOID
- (id) delegate; // GOOD
Variable Names
Variables
names start with a lowercase and use mixed case to delimit words. Class member
variables have prefix underscores. For example: localVariable,
_instanceVariable.
Common Variable Names
Do
not use Hungarian notation for syntactic attributes, such as the static type of
a variable (int or pointer). Give as descriptive a
name as possible, within reason. Don't worry about saving horizontal space as it is far more important to make your code
immediately understandable by a new reader. In names that involve a specifier, avoid prefix specifiers
and prefer suffix specifiers (errorCount
instead of numErrors). For example:
// Bad
int nerr;
int nCompConns;
tix = [[NSMutableArray alloc] init];
obj = [someObject
object];
p = [network port];
// Good
int errorCount;
int completedConnectionCount;
tickets = [[NSMutableArray alloc] init] autorelease];
userInfo = [someObject
object];
port = [network port];
Instance Variables
Instance
variables are mixed case and should be prefixed with an underscore, e.g. _nameTextField.
Constants
Constant
names (#defines, enums, const local variables, etc.)
should start with a capitalized 2 character prefix and
then use mixed case to delimit words, SPInvalidHandle,
SPWritePermissionReadOnly.
Comments
Though
a pain to write, they are absolutely vital to keeping our code readable. The
following rules describe what you should comment and where. But remember: while
comments are very important, the best code is self-documenting. Giving sensible
names to types and variables is much better than using obscure names and then
trying to explain them through comments.
When
writing your comments, write for your audience: the next contributor who will
need to understand your code. Be generous - the next one may be you!
File Comments
Start
each file with a copyright notice, followed by a description of the contents of
the file.
//
// PEPolygon.m
// PEPolygon
//
// Created by
Student on 11/11/11.
// Copyright
2009 Polygon. All rights reserved.
//
// Description
here.
//
Legal Notice and Author Line
Every
file should contain the following items, in order:
á
a copyright statement (for example, Copyright 2009 Student Name)
á
a license boilerplate. Choose the appropriate
boilerplate for the license used by the project (for example, Apache 2.0, BSD,
LGPL, GPL).
If
you make significant changes to a file that someone else originally wrote, add
yourself to the author line. This can be very helpful when another contributor
has questions about the file and needs to know whom to contact about it.
Declaration Comments
Every
interface, category, and protocol declaration should have an accompanying
comment describing its purpose and how it fits into the larger picture. In
header files, use doxygen standard comments so
documentation about the interface can be autogenerated.
Make reference to other object names so they can be interlinked. In source
files, use standard // comments
/**
PEPolygon is an abstract representation of a
classical polygon. */
@interface PEPolygon : NSObject
{
É
}
@end
If
you have already described an interface in detail in the comments at the top of
your file feel free to simply state "See comment
at top of file for a complete description", but be sure to have some sort
of comment.
Additionally,
each method in the public interface should have a comment explaining its
function, arguments, return value, and any side effects. Use doxygen standard Ò@Ó notation to mark parameters and return
types as necessary.
/**
Initializes a polygon with a name and side count
@param sideCount Number of
sides in the polygon
@param name String specifying the name of the polygon
*/
-
(id) initWithSideCount:(NSInteger)sideCount name:(NSString*)name;
Document
the synchronization assumptions the class makes, if any. If an
instance of the class can be accessed by multiple threads, take extra
care to document the rules and invariants surrounding multithreaded use.
Cocoa and Objective-C
Features
Identify Designated Initializer
Use
a designated initializer and comment and clearly
identify your designated initializer if it differs
from the superclass.
It
is important for those who might be subclassing your
class that the designated initializer be clearly
identified. That way, they only need to subclass a single initializer
(of potentially several) to guarantee their subclass' initializer
is called. It also helps those debugging your class in the future understand
the flow of initialization code if they need to step through it.
Override Designated Initializer
When
writing a subclass that requires an init... method, make sure you override the superclass' designated initializer.
If
you fail to override the superclass' designated initializer, your initializer may not be called in all cases, leading to
subtle and very difficult to find bugs.
Initialization
Don't
initialize variables to 0 or nil in the init method; it's redundant.
All
memory for a newly allocated object is initialized to 0 (except for isa), so don't clutter up the init method by
re-initializing variables to 0 or nil.
Keep the Public API Simple
Keep
your class simple; avoid "kitchen-sink" APIs. If a method doesn't
need to be public, don't make it so. Use a private category to prevent
cluttering the public header.
Private Category
Unlike
C++, Objective-C doesn't have a way to differentiate between public and private
methods — everything is public. As a result, avoid placing methods in the
public API unless they are actually expected to be used
by a consumer of the class. This helps reduce the likelihood they'll be called
when you're not expecting it. This includes methods that are being overridden
from the parent class. For internal implementation methods, use a category
defined in the implementation file as opposed to adding them to the public
header. All classes should have a private interface category. Add protocol
lists to the private category if knowledge of them being
adhered to is not part of the classes interface.
#pragma mark -
#pragma mark Private Interface
@interface PEPolygon () <PEShapeProtocol>
-
(void) verifyAnglesUsingRadians:(BOOL)usingRadians;
@end
You
should declare your private category using a class extension, for example:
@interface PEPolygon () { É }
This
will guarantee that the declared methods are implemented in the @implementation
section by issuing a compiler warning if they are not.
Again,
"private" methods are not really private. You could accidentally
override a superclass's "private" method,
thus making a very difficult bug to squash. In general, private methods should
have a fairly unique name that will prevent subclasses from unintentionally
overriding them.
Finally,
Objective-C categories are a great way to segment a large @implementation
section into more understandable chunks and to add new, application-specific
functionality to the most appropriate class. For example, instead of adding
"middle truncation" code to a random object in your app, make a new
category on NSString).
#import
and #include
#import Objective-C/Objective-C++ headers, and #include C/C++
headers.
Choose
between #import and #include based on the language of the header that you are
including.
á
When including a header that uses Objective-C or Objective-C++, use
#import.
á
When including a standard C or C++ header, use #include. The header
should provide its own #define guard.
Some
Objective-C headers lack #define guards, and expect to be included only by #import.
As Objective-C headers may only be included in Objective-C source files and
other Objective-C headers, using #import across the board is appropriate.
Standard
C and C++ headers without any Objective-C in them can expect to be included by
ordinary C and C++ files. Since there is no #import in standard C or C++, such
files will be included by #include in those cases. Using #include for them in
Objective-C source files as well means that these headers will always be
included with the same semantics.
This
rule helps avoid inadvertent errors in cross-platform projects. A Mac developer
introducing a new C or C++ header might forget to add #define guards, which
would not cause problems on the Mac if the new header were included with
#import, but would break builds on other platforms where #include is used.
Being consistent by using #include on all platforms means that compilation is
more likely to succeed everywhere or fail everywhere, and avoids the
frustration of files working only on some platforms.
#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#import "GTMFoo.h"
#include "base/basictypes.h"
Use Root Frameworks
Include
root frameworks over individual files.
While
it may seem tempting to include individual system headers from a framework such
as Cocoa or Foundation, in fact it's less work on the compiler if you include
the top-level root framework. The root framework is generally pre-compiled and
can be loaded much more quickly. In addition, remember to use #import rather
than #include for Objective-C frameworks.
#import <Foundation/Foundation.h> // good
#import <Foundation/NSArray.h> // avoid
#import <Foundation/NSString.h>
...
Prefer To autorelease At Time of Creation
When
creating new temporary objects, autorelease them on
the same line as you create them rather than a separate release later in the
same method.
While
ever so slightly slower, this prevents someone from accidentally removing the
release or inserting a return before it and introducing a memory leak. E.g.:
// AVOID (unless you have a compelling performance reason)
PEController* controller = [[PEController
alloc] init];
// ... code here that might return ...
[controller release];
// BETTER
PEController * controller = [[[PEController
alloc] init] autorelease];
Autorelease Then Retain
Assignment
of objects follows the autorelease then retain
pattern.
When
assigning a new object to a variable, one must first release the old object to
avoid a memory leak. There are several "correct" ways to handle this.
We've chosen the "autorelease then retain"
approach because it's less prone to error. Be aware in tight loops it can fill
up the autorelease pool, and may be slightly less
efficient, but we feel the tradeoffs are acceptable.
-
(void) setName:(NSString*)name
{
[_name autorelease];
_name = [name copy];
}
Setters copy NSStrings
Setters
taking an NSString, should always copy the string it
accepts.
Never
just retain the string. This avoids the caller changing it under you without
your knowledge. Don't assume that because you're accepting an NSString that it's not actually an NSMutableString.
Avoid Throwing Exceptions
Don't
@throw Objective-C exceptions, but you should be prepared to catch them from
third-party or OS calls.
We
do compile with -fobjc-exceptions (mainly so we get
@synchronized), but we don't @throw. Use of @try, @catch, and @finally are
allowed when required to properly use 3rd party code or libraries. If you do
use them please document exactly which methods you expect to throw.
Also
be aware when writing Objective-C++ code that stack
based objects are not cleaned up when you throw an Objective-C exception.
Example:
nil Checks
Use
nil checks for logic flow only.
Use
nil checks for logic flow of the application, not for crash prevention. Sending
a message to a nil object is handled by the Objective-C runtime. If the method
has no return result, you're good to go. However if there is one, there may be
differences based on runtime architecture, return size, and OS X version (see
Apple's documentation for specifics).
Note
that this is very different from checking C/C++ pointers against NULL, which
the runtime does not handle and will cause your application to crash. You still
need to make sure you do not dereference a NULL pointer.
BOOL Pitfalls
Use
the constants TRUE and FALSE instead of YES and NO. This maps better to any
other programming language.
Be
careful with BOOL return values and mixing bool with
BOOL. Avoid comparing directly with YES.
Only
use the constants YES and NO for BOOL return values and assignments. Common
mistakes include casting an array's size or a pointer value as a BOOL, which
depending on the value of the last byte, could still result in a NO value.
Also
be careful when interoperating with C++ and its bool
type. Don't just assume that YES and NO map directly to true and false. You can
use the ternary operator to succinctly map between them.
bool b = someBOOL
? true : false; // to go one way...
someBOOL = b ? TRUE : FALSE; // ... or the other
Finally,
don't directly compare BOOL variables directly with TRUE. Not only is this
harder to read for those well-versed in C, the first point above demonstrates
that return values may not always be what you expect.
BOOL great = [foo isGreat];
if (great == TRUE)
// ...be
great!
BOOL great = [foo isGreat];
if (great)
// ...be
great!
Properties
Properties
in general are allowed with the following caveat: properties are an Objective-C
2.0 feature which will limit your code to running on the iPhone
and MacOS X 10.5 (Leopard) and higher. Dot notation
to access properties is allowed on a project by project
basis.
Naming
A
property's associated instance variable's name must conform to the prefix
underscore requirement. The property's name should be the same as its associated
instance variable without the prefix underscore.
Use
the @synthesize directive to rename the property correctly.
Use Copy Attribute For
Strings
NSString properties should always be declared with the copy attribute.
This
logically follows from the requirement that setters for NSStrings
always must use copy instead of retain.
Atomicity
Be
aware of the overhead of properties. By default, all synthesized setters and
getters are atomic. This gives each set and get calls a substantial amount of
synchronization overhead. Declare your properties nonatomic
unless you require atomicity.
Dot notation
Some
projects have elected to not use dot notation to access properties. They do
this because:
1. Dot notation is purely
syntactic sugar for standard method calls, whose readability gains are
debatable. It just gives you another way to make method calls.
2. It obscures the type that
you are dereferencing. When one sees: [foo setBar:1] it is immediately clear that you are working with an
Objective-C object. When one sees foo.bar = 1 it is
not clear if foo is an object, or a struct/union/C++ class.
3. It allows you to do method
calls that look like getters.
4. It hides method calls.
Cocoa Patterns
Delegate Pattern
Delegate
objects should not be retained.
A
class that implements the delegate pattern should:
1. Have an instance variable
named _delegate to reference the delegate.
2. Thus, the accessor methods should be named delegate and setDelegate:.
3. The _delegate object should
not be retained.
Model/View/Controller
Separate
the model from the view. Separate the controller from the view and the model.
Use @protocols for callback APIs.
á
Separate model from view: don't build assumptions about the
presentation into the model or data source. Keep the interface between the data
source and the presentation abstract. Don't give the model knowledge of its
view. (A good rule of thumb is to ask yourself if it's
possible to have multiple presentations, with different states, on a single
instance of your data source.)
á
Separate controller from view and model: don't put all of the
"business logic" into view-related classes; this makes the code very
unusable. Make controller classes to host this code, but ensure that the
controller classes don't make too many assumptions about the presentation.
á
Use a protocol for callback APIs where all the methods must be
implemented. Use a category (or an "informal protocol") when not all
the methods need to be implemented.