Home

Awesome

Robots & Pencils Objective-C Style Guide

This documents the coding style adhered to at Robots & Pencils, enjoy! Feel free to open a pull request if you think we are missing anything or would like to debate existing points.

Table of Contents generated with DocToc

Whitespace

#import <AwesomeFramework/AwesomeFramework.h>
#import <AnotherFramework/AnotherFramework.h>

#import "SomeDependency.h"
#import "SomeOtherDependency.h"

@interface MyClass
@interface MyClass ()

// Properties - empty line above and below

@end

@implementation MyClass

// Body - empty line above and below

@end

- (CGSize)intrinsicContentSize {
    return CGSizeMake(12, 12);
}

#pragma mark - Private

- (void)setup {
    [self addGestureRecognizer:[[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(clicked:)]];
}
NSInteger index = rand() % 50 + 25; // arc4random_uniform(50) should be used insted of `rand() % 50`, but doesn't illustrate the principle well
index++;
index += 1;
index--;
if (alpha + beta <= 0) && (kappa + phi > 0) {
}

Good:

// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
    // something
} completion:^(BOOL finished) {
    // something
}];

Bad:

// colon-aligning makes the block indentation wacky and hard to read
[UIView animateWithDuration:1.0
                 animations:^{
                     // something
                 }
                 completion:^(BOOL finished) {
                     // something
                 }];

Good:

- (void)awakeFromNib {
    UIStoryboard *signatureStoryboard = [UIStoryboard storyboardWithName:@"BBPopoverSignature" bundle:nil];
    self.signatureViewController = [signatureStoryboard instantiateViewControllerWithIdentifier:@"BBPopoverSignature"];
    self.signatureViewController.modalPresentationStyle = UIModalPresentationPopover;
    self.signatureViewController.preferredContentSize = CGSizeMake(BBPopoverSignatureWidth, BBPopoverSignatureHeight);
    self.signatureViewController.signatureImageView = self;
    
    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(initiateSignatureCapture)];
    [self addGestureRecognizer:tapRecognizer];
}

Note:

  1. all the signatureViewController-related lines are together
  2. the new line delimits the end of configuration of signatureViewController
  3. the tapRecognizer instantiation and configuration is grouped, and not mixed with unrelated code
  4. a new line after the opening { and a new line before the closing } are permissible. In some cases they aid readability and in others they yield an overabundance of whitespace.

Good:

- (NSAttributedString *)aboutTermsAttributedString {

    NSDictionary *attributes = nil;
    NSError *error = nil;

    NSURL *fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"terms_of_use" ofType:@"html"]];
    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithFileURL:fileURL 
                                                                               options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                                                    documentAttributes:&attributes
                                                                                 error:&error];
    if (error) {
        NSLog(@"Error: unable to load about mobile string. %@", [error localizedDescription]);
        return nil;
    }

    return attributedString;
}

Note:

  1. blank line after the opening { of the method helps give the local variables their own context
  2. complexity of attributedString initialization is more readable with colon aligning
  3. final return value is immediately clear thanks to the blank line above it

Good:


@interface BBProofOfLossViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, BBAutocompletePopoverDateTextFieldDelegate, BBPopoverSignatureImageViewDelegate>

@property (strong, nonatomic) NSArray *targetItems;
@property (strong, nonatomic) BBCustomForm *customForm;

@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *addressLabel;
@property (weak, nonatomic) IBOutlet UILabel *fooLabel;
@property (weak, nonatomic) IBOutlet UILabel *barLabel;
@property (weak, nonatomic) IBOutlet UILabel *instanceNumberLabel;
@property (weak, nonatomic) IBOutlet UILabel *relatedNumberLabel;
@property (weak, nonatomic) IBOutlet UILabel *bazLabel;
@property (weak, nonatomic) IBOutlet UILabel *typeOfInstanceLabel;
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *itemListHeightConstraint;

@property (weak, nonatomic) IBOutlet BBAutocompletePopoverDateTextField *formDatePopoverTextField;

@property (weak, nonatomic) IBOutlet BBPopoverSignatureImageView *witnessSignatureImageView;
@property (weak, nonatomic) IBOutlet UITextField *witnessSignatureBackgroundTextField;
@property (weak, nonatomic) IBOutlet UILabel *witnessNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *witnessLocationLabel;
@property (weak, nonatomic) IBOutlet UILabel *witnessDateLabel;

@property (weak, nonatomic) IBOutlet BBPopoverSignatureImageView *submitterSignatureImageView;
@property (weak, nonatomic) IBOutlet UITextField *submitterSignatureBackgroundTextField;
@property (weak, nonatomic) IBOutlet UILabel *submitterNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *submitterLocationLabel;
@property (weak, nonatomic) IBOutlet UILabel *submitterDateLabel;

@property (weak, nonatomic) IBOutlet BBPopoverSignatureImageView *authorizerSignatureImageView;
@property (weak, nonatomic) IBOutlet UITextField *authorizerSignatureBackgroundTextField;
@property (weak, nonatomic) IBOutlet UILabel *authorizerNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *authorizerLocationLabel;
@property (weak, nonatomic) IBOutlet UILabel *authorizerDateLabel;

@end

Note:

  1. new line after the @interface and before the @end
  2. properties that are not IBOutlets are grouped
  3. IBOutlet properties are grouped by context
  4. the patterns in the grouping aid readability by allowing the eye to see inconsistencies (there are none in this case)

Bad:


- (void)viewController:(BBViewController *)viewController
        finishedWithAuth:(BBAuthentication *)auth
        error:(NSError *)error {

    //pushViewController didn't work, use ugly full screen view
    [viewController dismissViewControllerAnimated:YES completion:nil];

    if (error != nil) {

        NSLog(@"Authentication failed %@", [error description]);

    } else {

        NSString *apiURL = @"https://api.example.com/v1/quux/~?format=json";

        NSURL *url = [NSURL URLWithString:apiURL];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        [auth authorizeRequest:request completionHandler:^(NSError *error) {

            AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
            operation.responseSerializer = [AFJSONResponseSerializer serializer];
            [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

                NSDictionary *responseData = (NSDictionary *)responseObject;
                RBKQuickAlert(@"Response description", responseData.description);

            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

                RBKQuickAlert(@"Response error", error.description);

            }];

            [operation start];

        }];

    }

}

Note:

  1. method colons aren't aligned. (Method signature is not well suited to colon aligning)
  2. Happy path is nested. (Early return is missing)
  3. Lots of extra blank lines that break up readability. (Whitespace is failing to define context)
  4. URL string is hardcoded (not environment-aware)
  5. NSMutableURLRequest instantiation is spread over several lines

Bad rewritten better:


// DEV_BUILD/STAGE_BUILD/PROD_BUILD are configured in Project Build Settings Preprocessor Macros
#if DEV_BUILD
static NSString * const BBAPIBaseURLString = @"https://dev.example.com/v1/quux/~?format=json"; // dev

#elif QA_BUILD 
static NSString * const BBAPIBaseURLString = @"https://qa.example.com/v1/quux/~?format=json"; // qa

#elif STAGE_BUILD
static NSString * const BBAPIBaseURLString = @"https://stage.example.com/v1/quux/~?format=json"; // stage

#elif PROD_BUILD
static NSString * const BBAPIBaseURLString = @"https://api.example.com/v1/quux/~?format=json"; // prod

#else
static NSString * const BBAPIBaseURLString = @"https://api.example.com/v1/quux/~?format=json"; // prod

#endif

- (void)viewController:(BBViewController *)viewController finishedWithAuth:(BBAuthentication *)auth error:(NSError *)error {

    //pushViewController didn't work, use ugly full screen view
    [viewController dismissViewControllerAnimated:YES completion:nil];

    if (error) {
        NSLog(@"Authentication failed %@", [error localizedDescription]);

        // TODO: call our own completion block with this error?

	     return;
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:BBAPIBaseURLString]]; // mutable in case we need to adjust the request headers
    [auth authorizeRequest:request completionHandler:^(NSError *error) {
        
        // we should probably be checking the supplied error to decided if we need to do this operation or not
        
        AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
        operation.responseSerializer = [AFJSONResponseSerializer serializer];
        [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
            NSDictionary *responseData = (NSDictionary *)responseObject;
            
            // TODO: Handle our response data. Call our own completion block?
            
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"Error: %@", error.localizedDescription);
            
            // TODO: call our own completion block when we have an error?
        }];
        
        [operation start];
    }];
}

Note: This method is obviously incomplete and may not, architecturally be optimal, however it can be still styled in a readable manner.

Organization

#pragma mark - Lifecycle

+ (instancetype)objectWithThing:(id)thing {}
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors

- (void)setCustomProperty:(id)property {}
- (id)anotherCustomProperty {}

#pragma mark - Actions

- (IBAction)submitData:(id)sender {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject

- (NSString *)description {}

Comments

When they are needed, comments should be used to explain why a particular piece of code does something. Any comments that are used must be kept up-to-date or deleted.

Block comments should generally be avoided, as code should be as self-documenting as possible, with only the need for intermittent, few-line explanations. Exception: comments used to generate documentation.

Literals

NSString, NSDictionary, NSArray, and NSNumber literals should be used whenever creating immutable instances of those objects. Pay special care that nil values not be passed into NSArray and NSDictionary literals, as this will cause a crash.

Good:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

Bad:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];

Declarations

Good:

@property (assign, nonatomic) NSInteger statusCode;

Bad:

@property (nonatomic) NSInteger statusCode;

Good:

@synthesize statusCode = _statusCode;

Bad:

@synthesize statusCode;

Good:

@property (weak, nonatomic) id<SGOAnalyticsDelegate> analyticsDelegate;

Bad:

@property (nonatomic) id <SGOAnalyticsDelegate> analyticsDelegate;

Private Properties

Private properties should be declared in class extensions (anonymous categories) in the implementation file of a class. Named categories, used when extending an existing class (e.g. NSString), should not be confused with class extensions.

For example:

@interface VPDetailViewController ()

@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;

@end

Expressions

Good:

view.frame

Bad:

[view frame]

Good:

self.view.frame = CGRectMake(x, y, width, height);
[self.view removeFromSuperview];

Person *selectedPerson = [self getPersonWithId:idNumber];
self.nameLabel.text = selectedPerson.name;

- (void)setProperty:(id)newValue {
    _property = newValue;
    [self someOtherMethod];
}

Bad:

[[self view] setFrame:CGRectMake(x, y, width, height)];
[_view removeFromSuperview];

self.nameLabel.text = [self getPersonWithId:idNumber].name;

Good:

NSUInteger numberOfItems = sampleArray.count;

sampleView.backgroundColor = [UIColor greenColor];

Bad:

int numberOfItems = [sampleArray count]; // using int and explicit message sending

[sampleView setBackgroundColor:[UIColor greenColor]]; // explicit message sending
void *ptr = &value + 10 * 3;
NewType a = (NewType)b;

for (NSInteger i = 0; i < 10; i++) {
    doCoolThings();
}

Control Structures

if (!goodCondition) return;

if (condition == YES) {
    // do stuff
} else {
    // do other stuff
}

If the name of a BOOL property is expressed as an adjective, the property can omit the “is” prefix but specifies the conventional name for the get accessor, for example:

@property (assign, getter=isEditable) BOOL editable;

Exceptions and Error Handling

Blocks

void (^blockName1)(void) = ^{
    // do some things
};

id (^blockName2)(id) = ^ id (id args) {
    // do some things
};

Singletons

Singleton objects should use a thread-safe pattern for creating their shared instance.

+ (instancetype)sharedInstance {
   static id sharedInstance = nil;

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });

   return sharedInstance;
}

Code Naming

Good

Bad

Method Signatures

Use lower camel case

Good:

answerViewController

Bad:

answer_view_controller

Start with action

For methods that represent an action an object takes, start the name with the action.

Good:

- (IBAction)showDetailViewController:(id)sender

Bad:

- (void)detailButtonTapped:(id)sender

Getters

If the method returns an attribute of the receiver, name the method after the attribute. The use of "get" is unnecessary, unless one or more values are returned via indirection.

Good:

- (NSInteger)age

Bad:

- (NSInteger)calcAge
- (NSInteger)getAge

Return Type Spacing

For consistency method definitions should have a space between the + or - (scope) and the return type (matching Apple's style).

Good:

- (int)age

Bad:

-(int)age
-(int) age

Parameter Keywords

Use keywords before all parameters.

Good:

- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;

Bad:

- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;

Be Descriptive

Make the word before the argument describe the argument.

Good:

- (id)viewWithTag:(NSInteger)tag;

Bad:

- (id)taggedView:(NSInteger)tag;

Note: this is a poor example because in general tags are an antipattern and should be avoided if at all possible.

Avoid And (With Exceptions)

Do not use "and" to link keywords that are attributes of the receiver.

Good:

- (NSInteger)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;

Bad:

- (NSInteger)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;

Exception: if the method describes two separate actions, use "and" to link them:

- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

Signature Spacing

Method parameters should not have a space between the keyword and the parameter.

Good:

- (void)setExample:(NSString *)text;

Bad:

- (void)setExample: (NSString *)text;
- (void)setExample:(NSString *) text;

Init Methods

Init methods should return instancetype instead of id. Generally this is the one place ivars should be used instead of properties because the class may be in an inconsistent state until it is fully initialized.

- (instancetype)init {
  self = [super init];
  if (self) {
    // ...
  }
  return self;
}

Dealloc Methods

Dealloc methods are no longer required when using arc but in certain cases must be used to remove observers, KVO, etc.

- (void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Classes

Class names use upper camel case, ie First word capitalized, start of new words capitalized as well. In general there should be one class per .h/.m file.

SVSSpy
SVSWhiteSpy
SVSBlackSpy
SVSCar
SVSCarEngine

Namespace prefixes

Descriptive names should generally avoid conflicts, however there are tangible benefits to using three character class name prefixes e.g. RBKObjectSerialization. Class name prefixes can be used to:

Shared code should be definitely be prefixed (e.g. in RoboKit).

Class name prefixes may be avoided for CoreData entity names.

Avoid using overly simple names like "Model" "View" "Object".

When you don't prefix and you have a namespace collision they're megahard to debug and unravel.

Booleans

Objective-C uses YES and NO. Therefore true and false should only be used for CoreFoundation, C or C++ code. Since nil resolves to NO it is unnecessary to compare it in conditions.

Good:

if (someObject) {}
if (![anotherObject boolValue]) {}

Bad:

if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}

Properties

Pointer Spacing

The * should be nearest the variable, not the type.

Good:

NSString *text = @"foo";

Bad:

NSString * text = @"foo";
NSString* text = @"foo";

@synthesize and @dynamic

Note: as of Xcode 4.4/4.5 @synthesize should be removed with some exceptions. See (http://useyourloaf.com/blog/2012/08/01/property-synthesis-with-xcode-4-dot-4.html) "When To Supply The Instance Variable" for clarity

@synthesize and @dynamic should go directly beneath @implementation where each @synthesize and @dynamic should be on a single line.

When synthesizing the instance variable, use @synthesize var = _var; as this prevents accidentally calling var = blah; when self.var = blah; is correct.

@property

Property definitions should be used in place of ivars. When defining properties, put strong/weak/retain/assign first then nonatomic for consistency.

@property (weak, nonatomic) IBOutlet UIWebView *messageWebView;

*Note: IBOutlets should be weak unless they are a top-level item in a xib (e.g. the top-level view is strong, anything beneath it is weak)

Info about the overhead and performance of properties vs ivars can be found here.

Enums

Magic numbers that are integers should be stored in an enum. If you want to use it as a type, you can make it a type:

typedef NS_ENUM(NSUInteger, VPLeftMenuTopItemType) {
    VPLeftMenuTopItemTypeMain = 0,
    VPLeftMenuTopItemTypeShows,
    VPLeftMenuTopItemTypeSchedule,
    VPLeftMenuTopItemTypeWatchLive,
    VPLeftMenuTopItemTypeMax,
};

In this case each successive item in the enum will have an integer value greater than the previous item.

Naming in this ^ style also makes the enum Swift-compatible. (e.g. .Main, .WatchLive)

You can also make explicit value assignments:

typedef NS_ENUM(NSInteger, RBKGlobalConstants) {
    RBKPinSizeMin = 1,
    RBKPinSizeMax = 5,
    RBKPinCountMin = 100,
    RBKPinCountMax = 500,
};

Older k-style constant definitions should be avoided unless writing CoreFoundation C code (unlikely).

Bad:

enum GlobalConstants {
    kMaxPinSize = 5,
    kMaxPinCount = 500,
};

xib Files

xib files should always be saved in their language-specific folders. The default folder for English is en.lproj. xib file names should end in view, menu or window but never controller (as xibs are not controllers).

Give descriptive labels to views to make them easier to recognize, preferrably the same name as the referencing outlet if there is one.

Golden Path

When coding with conditionals, the left hand margin of the code should be the "golden" or "happy" path. That is, don't nest if statements. Multiple return statements are ok.

Good:

- (void)someMethod {
    if (![someOther boolValue]) return;
    //Do something important
}

Bad:

- (void)someMethod {
    if ([someOther boolValue]) {;
        //Do something important
    }
}

More on Brackets

Method brackets should be at the end of the line, preceded by a single space

Good:

- (void)checkCondition {
    if (foo) {
       NSLog(@"Woof!");
    } else {
       NSLog(@"Meow!");
    }
}

Prevents Bugs:

if (foo) {
   NSLog(@"Moof!");
}

Exception for Condition Checking:

if (condition) return;

Note: Apple's templates sometimes have the opening bracket on a new line flush left, but generally at the end of the line.

- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"RootViewControllerCellIdentifier";
    ....
}

Memory Management

If you're not using ARC you probably need to talk to the client about increasing the minimimum target OS version.

Boyscout / Girl Guide

Always leave the code in better condition than you found it.

Copyrights

By default, use a Robots & Pencils copyright. There may be special circumstances where it should be changed to a customer name.

//  Copyright (c) 2012 Robots and Pencils Inc. All rights reserved.

Set the Organization in your .xcodeproj to override your default company name for new files that are created. Select the project in File Navigator, then use the File Inspector (first button) in the Utilities pane. Set the organization to Robots and Pencils Inc.

Proper Code Nutrition

Avoid magic numbers with no meaning, preferring instead a named variable or constant (see following examples).

Integers

Bad:

if ([pin length] < 5)

What is this checking for?

Good:

if ([pin length] > RBKPinSizeMax)

We can see that this is checking if the pin is longer than the max allowed.

Floats

In a (top level) .h you can define the float:

extern const float RBKFooFloat;

and then in the relevant .m file assign it a value:

const float RBKFooFloat = 18.0;

Strings

In a (top level) .h you can define the string:

extern NSString * const RBKLocationsDatabaseName;

and then in the relevant .m file assign it a value:

NSString * const RBKLocationsDatabaseName = @"locations.db";

Note: the above is a constant pointer to an NSString object while the following is a pointer to a constant NSString object.

Bad:

const NSString * RBKConstantString = @""; // pointer to constant
// which is equivalent to
NSString const * RBKConstantString = @"";

Basic Code Principles

Testing

Inspiration

Apple

Scott Stevenson

Marcus Zarra

Github

NYTimes