Macoscope Objective-C Style Guide
This style guide describes a set of basic rules that we follow here at Macoscope. We strive to write better code every day, and we believe this document outlines the best practices that will help you do the same.
This document is based on, and inspired by, the awesome NYTimes Objective-C Style Guide.
Also, we're hiring.
Mandatory Reading
If you haven't read the Coding Guidelines for Cocoa yet, it's probably a good idea to stop right here and come back only after you're finished with it. You should pay special attention to:
Table of Contents
- Dot-Notation Syntax
- Spacing
- Error handling
- Methods
- Variables
- Naming
- Comments
- Init and Dealloc
- Literals
- Categories
- CGRect Functions
- Constants
- Enumerated Types
- Bitmasks
- Private Properties
- Booleans
- Singletons
- Xcode Project
- Refactoring
- Miscellaneous
Dot-Notation Syntax
Property? Dot-notation. Always.
Not a property? Brackets.
If Apple brings their frameworks up to date with modern ObjC syntax (looking at you, NSArray.count
!), feel free to use dot-notation when targeting earlier releases.
For example:
array.count;
[UIApplication sharedApplication].delegate;
Not:
[array count];
UIApplication.sharedApplication.delegate;
Spacing
- 4 spaces. Soft tabs. Period.
- When working with older projects that used wrong number of spaces, adapt to the existing style. Do not convert existing projects to new guidelines or you'll break
git blame
.
- When working with older projects that used wrong number of spaces, adapt to the existing style. Do not convert existing projects to new guidelines or you'll break
- 1TBS for control statements.
- K&R for method declaration. (Opening brace appears in the following line.)
- Never omit braces in control statements,
goto fail;
should be a convincing enough argument for everyone.
For example:
- (void)describeCodingStyleWithCodingStyle:(MCSCodingStyle)style
{
if (MCSCodingStyleKAndR == style) {
[self doSomething];
}
}
- Separate methods with one empty line. Use whitespace in methods as needed, but if you find yourself separating multiple groups of statements, there's a good chance they should be split into separate methods.
Ternary Operator
If you're writing (or, for that matter, reading) multiple nested ternary operators, it means that something, somewhere went wrong. Write it in a different way (if
statement, instance variable) and refactor existing occurrences.
For example:
goodThing = a > b ? x : y;
Not:
badThing = a > b ? x = c > d ? c : d : y;
Error handling
If you haven't already, you should probably read @mattt's excellent write-up of NSError
on NSHipster.
TL;DR version: Check for returned value, not error variable.
For example:
NSError *error;
if (![self magicalOperationThatCanResultInError:&error]) {
// whoops. Better handle this!
}
Not:
NSError *error;
[self magicalOperationThatCanResultInError:&error];
if (error) {
// whoops. Better handle this!
}
But really, read that NSHipster article.
Methods
Follow Apple API examples and docs. A space between +
/-
and method name, a space between method segments, a space between type and asterisk. No spaces anywhere else.
For example:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
Not:
- (BOOL) tableView: (UITableView *) tableView canMoveRowAtIndexPath: (NSIndexPath *) indexPath;
Multiargument methods like new lines between arguments, colon-aligned.
For example:
- (void)thisViewController:(ThisViewController *)thisViewController
didFinishWithThing:(Thing *)thing
somethingElse:(SomethingElse *)somethingElse
style:(Style)style;
- (void)prepareCakeWithIngredients:(NSArray *)ingredients
success:(void(^)(Cake *deliciousCake))success
failure:(void(^)(NSError *))failure;
Variables
Make things as simple as possible, but not simpler.
If you're unsure which side you fall on, it's better to err on the side of wordiness.
There shouldn't be any reason to use one-letter variable names apart from for()
loops.
Asterisks hug the variable name, not the type.
For example:
NSString *macoscopeString;
Not:
NSString* macoscopeString;
Use properties over naked instance variables.
Generally avoid directly accessing instance variables, except in initializer methods (init
, initWithCoder:
, etc…), dealloc
methods and within custom setters and getters. See Advanced Memory Management Programming Guide for rationale.
For example:
@interface MCSObject: NSObject
@property (nonatomic, strong) NSString *title;
@end
Not:
@interface MCSObject : NSObject {
NSString *title;
}
Variable Qualifiers
When it comes to the variable qualifiers introduced with ARC, the qualifier (__strong
, __weak
, __unsafe_unretained
, __autoreleasing
) should be placed first, e.g., __weak NSString *text
. This is technically incorrect, but much more clear since it immediately stands out (and is allowed by the compiler).
Property attributes
Property attributes are declared in the following order:
- Atomicity (
nonatomic
) - Storage type (
weak
,strong
,copy
, etc.) - Visibility (
readonly
,readwrite
) - Custom getters and setters
For example:
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, weak) IBOutlet UILabel *detailDescriptionLabel;
@property (nonatomic) BOOL selected;
Naming
Apple’s naming conventions should be adhered to wherever possible, especially those related to memory management rules (NARC).
For example:
UIButton *magicButton;
Not:
UIButton *magBut;
Use MCS prefixes for internal code. Three-letter class prefixes are preferred, but Apple doesn't respect their own guidelines in this regard anyway (see Mantle and Metal). You can omit them for Core Data entity names.
Externally visible constants should be written in camel case with all words capitalized and prefixed by the related class name for clarity.
For example:
// MCSMagicalCollectionView.h
extern NSString * const MCSMagicalCollectionViewDidSparkleNotification;
Not:
static const NSTimeInterval animDuration = 0.5;
Properties and local variables should be in camel case with the leading word being lowercase.
Instance variables should be in camel case with the leading word being lowercase, and should be prefixed with an underscore. This is consistent with instance variables synthesized automatically by LLVM. If LLVM can synthesize the variable automatically, then let it.
For example:
@synthesize descriptiveVariableName = _descriptiveVariableName;
Not:
id varnm;
Comments
Use common sense. Explain why you’re doing something, not the steps you’re taking.
Never commit code that has been commented out (take a second and go through git diff
before applying your changes, make sure you're leaving codebase in better shape than before, no one likes to deal with a messy code).
Documentation comments are good. Use them when they're actually useful and needed. If you can refactor something so that you don't need to explain it, do that instead.
Init and Dealloc
init
methods go at the top. dealloc
goes right below them. Consider wrapping them in #pragma mark - Lifecycle
or something similar.
init
methods should be structured like this:
- (instancetype)init {
self = [super init]; // or call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
Where applicable, always return instancetype
.
Literals
Then Apple said, "I give you
NSDictionary
,NSArray
,NSString
, andNSNumber
literals." And Apple saw everything it had made, and behold, it was very good.
No, really. They're good for you. Use them.
For example:
NSArray *names = @[@"Agata", @"Ania", @"Bartek", @"Daniel", @"Dawid", @"Dominik", @"Jarek",
@"Karol", @"Klaudia", @"Maciej", @"Maciej", @"Marcin", @"Rafał", @"Wojtek",
@"Wojtek", @"Zbigniew", @"Janek"];
NSDictionary *animals = @{@"dogs" : @[@"Boomer"]};
NSNumber *officeIndoorSwimmingPool = @YES;
NSNumber *over9000 = @9001;
Categories
Categories should be declared in separate files called ClassName+CategoryName.{h,m}.
There are no well-defined semantics for defining the same method in more than one category. In order to avoid undefined behavior, add a prefix to method names in categories on framework classes (e.g. NSArray
, UIImageView
, AFHTTPRequestOperationManager
). Don't add a prefix to method names in categories on your own classes.
For example:
// NSArray+MCSCollectionUtility.h
@interface NSArray (MCSCollectionUtility)
- (void)mcs_each:(void(^)(id object))block;
- (void)mcs_eachWithIndex:(void(^)(id object, NSUInteger index))block;
...
// MCSMagicalCollectionView+AbraCadabra.h
@interface MCSMagicalCollectionView (AbraCadabra)
- (void)abraCadabra;
- (void)hocusPocus;
...
CGRect Functions
When accessing the x
, y
, width
, or height
of a CGRect
, always use the CGGeometry
functions instead of direct struct member access. From Apple's CGGeometry
reference:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
For example:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
Not:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
In most common scenarios, when you want to get the size of a view, refer to its bounds
and not frame
. You can read this post to know why it might not be safe to base off of frame
.
CGRect bounds = self.view.bounds;
CGFloat width = CGRectGetWidth(bounds);
CGFloat height = CGRectGetHeight(bounds);
Try not to cut corners when you're adding a view which fills the entire superview. UIScrollView
changes bounds
when changing contentOffset
.
CGRect superBounds = self.view.bounds;
UIView *subview = [[UIView alloc] initWithFrame:CGRect(0, 0, CGRectGetWidth(superBounds), CGRectGetHeight(superBounds)];
Instead of:
CGRect superBounds = self.view.bounds;
UIView *subview = [[UIView alloc] initWithFrame:superBounds];
Remember about convertRect:toView:
and convertRect:fromView:
methods when working with frames. They also handle the UIScollView
bounds
well.
CGRect frameWithoutTransformations = [scrollView convertRect:scrollView.bounds toView:scrollView.superview];
Constants
Magic values should be avoided. If you do need to define a constant, it should be a static
constant.
Avoid #define
s. Unless you're writing an actual macro. Then, by all means, go ahead.
You may use Hungarian notation for implementation-level constants.
For example:
// MCSCompany.h
extern NSString * const MCSCompanyTagline;
// MCSCompany.m
NSString * const MCSCompanyTagline = @"We make award-winning apps for Apple devices.";
static NSString * const kOfficeLocation = @"Warsaw, Poland";
Not:
#define CompanyTagline @"We make award-winning apps for Apple devices."
#define maxNumberOfRows 2
Enumerated Types
Use NS_ENUM(). Not sure why? NSHipster has you covered.
NS_ENUM also provides Swift interoperability.
For example:
// MCSFishEye.h
typedef NS_ENUM(NSInteger, MCSFishEyeState) {
MCSFishEyeStateCollapsed,
MCSFishEyeStateExpandedActive,
MCSFishEyeStateExpandedPassive
};
// SomeFile.swift
fishEyeView.state = .Collapsed
Bitmasks
NS_OPTIONS(). For the same reasons as stated earlier with regards to NS_ENUM(). Again, read the article.
For example:
typedef NS_OPTIONS(NSUInteger, MCSCustomerHappiness) {
MCSCustomerHappinessVeryHapy = 1 << 0,
MCSCustomerHappinessExtremelyHappy = 1 << 1,
MCSCustomerHappinessEnormouslyHappy = 1 << 2
};
Private Properties
Private properties should be declared in class extensions (anonymous categories) in the implementation file of a class. Named categories (such as MCSPrivate
or private
) should never be used unless you’re extending another class.
For example:
@interface MCSFishEyeView()
@property (nonatomic, strong) NSArray *itemContainers;
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) UIView *transformView;
@end
Custom getters
There's no real consensus here. But remember, custom getters are custom for a reason. In most cases, when you have a property, the default getter should work just fine. That's what most developers expect. They see a header file and expect the default behaviour.
If you want to lazy load somethings, to have a singleton object or to have a readonly
property whose result has to be calculated (ex. count
of NSArray
), feel free to create one. Just make sure you have a good reason to do so.
Subclassing and Testing
Private properties and methods that need to be visible to some parts of the code for subclassing or testing purposes should be declared in class extensions in separate header files. To indicate the private nature of the header, the filename should follow the ClassName_Description.h
format (notice an _
used instead of a +
to differentiate from category headers).
For example:
// MCSSomeObject_PrivateProperties.h
@interface MCSSomeObject ()
@property (nonatomic) NSInteger someProperty;
@end
// MCSSomeObject.m
#import "MCSSomeObject.h"
#import "MCSSomeObject_PrivateProperties.h"
Booleans
Since nil
resolves to NO
, it is unnecessary to compare it in conditions. Never compare something directly to YES
, because YES
is defined to 1 and a BOOL
can be up to 8 bits.
This allows for more consistency across files and greater visual clarity.
For example:
if (!someObject) {
}
Not:
if (someObject == nil) {
}
For a BOOL
, here are two examples:
if (isAwesome)
if (![someObject boolValue])
Not:
if ([someObject boolValue] == NO)
if (isAwesome == YES) // Never do this.
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;
}
This will prevent possible and sometimes prolific crashes.
Try not to create excessive singletons. Perhaps what you're trying to do could be accomplished much easier using dependency injection?
Xcode Project
The physical files should be kept in sync with the Xcode project files in order to avoid file sprawl. Any Xcode groups created should be reflected by folders in the filesystem. Code should be grouped not only by type, but also by feature for greater clarity. You can use Synx by @venmo to organize the files on your disk for you.
When possible, always turn on "Treat Warnings as Errors" in the target's Build Settings and enable as many additional warnings as possible. If you need to ignore a specific warning, use Clang's pragma feature.
Refactoring
When you add/change code and you see that the nearby code is bad (e.g. doesn't comply with the guidelines you're reading right now), change it.
Example: you want to add a constant at the top of a file and you see that already present constants use #define instead of const. First, in a separate commit, change #define to const, then add your constant.
Miscellaneous
- Given how long Objective-C identifiers can get, it's rather pointless to enforce the hard line length limit. However, it's also important to not get carried away, so turn on page guide at column 100. Don't be afraid to break it when it increases code clarity.
- Don't be afraid of multiple return statements. Your code should follow the golden path. Multiple nested conditionals should be avoided.
Other Objective-C Style Guides
If ours doesn't fit your tastes, have a look at some other style guides:
- NYTimes Objective-C Style Guide
- GitHub
- Adium
- Sam Soffes
- CocoaDevCentral
- Luke Redpath
- Marcus Zarra
Acknowledgements
This document is largely based on the NYTimes Objective-C Style Guide, with sections we agreed with and felt we couldn't improve upon pulled verbatim from it.
It's also heavily influenced by and references NSHipster in multiple places.