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.

 

Objective-C++

 

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.