Objective-C Coding Standards
When In Doubt
If something isn't explicitly covered here you should generally refer to the NYTimes Objective-C Style Guide. If you can't find a definitive answer there check with your colleagues and codify the answer here.
Musts
Three-letter Prefix
All Objective-C projects should have a designated three-letter prefix, used on all classes (and any symbols exposed at the global level).
Code Styling
-
Leave a space before conditional parenthesis and the opening brace:
if (condition) { [self callSomeFunction]; }
-
Start code blocks for flow control statements on the same line:
if (condition) { ... } else if (condition) { ... } while (condition) { ... }
-
Format
switch
statements as follows, with one line per case:switch (indexPath.section) { case 0: thing = [self objectAtIndex:]; break; case 1: [self callSomeFunction]; break; default: [self bananasForFree]; }
Blank lines may be inserted between cases if it improves readability. If you need to declare a local variable at the level of a single
case
, you'll need to wrap the contents of thecase
in curly braces{ … }
:case 0: { UITableViewCell *cell = … } break;
-
Use dot syntax for accessing properties.
-
Use
self.property
instead of directly accessing member variable. This is more maintainable should you need to modify setters and getters in the future. The only exception to this rule is when writing initialization or custom accessor methods. -
When declaring structs or struct types, put the opening curly brace on the first line of the declaration. Name the struct with a singular noun, and name each field using camelCase:
// In the Constants.h file FOUNDATION_EXPORT const struct XXXCoreDataName { __unsafe_unretained NSString *modelName; __unsafe_unretained NSString *sqliteFileName; } XXXCoreDataName; // In the Constants.m file const struct XXXCoreDataName XXXCoreDataName = { .modelName = @"ExampleAppName", .sqliteFileName = @"ExampleAppName.sqlite", };
When implementing private structs while using Objective-Clean, use the
##OBJCLEAN_SKIP##/##OBJCLEAN_ENDSKIP#
macros to silence the compiler:static const struct { __unsafe_unretained NSString *token; __unsafe_unretained NSString *bundleID; __unsafe_unretained NSString *certificateType; __unsafe_unretained NSString *tokenIdentifier; //##OBJCLEAN_SKIP## //Otherwise whines about line below method closing brace. } VOKPushJSONKey = { //##OBJCLEAN_ENDSKIP## .token = @"token", .bundleID = @"bundle_identifier", .certificateType = @"certificate_type", .tokenIdentifier = @"id", };
-
Use Objective-C Literals instead of
[<CLASS NAME> initWith...]
whenever possible. See Clang Objective-C Literals.-
Example:
NSString *aString = @"Hello World"; NSNumber *aNumber = @23; NSNumber *aBooleanNumber = @NO; NSArray *anArray = @[ object1, object2, object3, ]; // NOTE: Not nil terminated. NSDictionary *aDictionary = @{ @"key1": value1, @"key2": value2, };
For simple literals like
@23
,@NO
, and@YES
, "boxing" (using the@()
construct) is unnecessary.-
Okay:
NSNumber *aBooleanNumber = @(NO);
-
Better:
NSNumber *aBooleanNumber = @NO;
-
-
For property getters that return simple literals or single-token expressions (e.g. returning the value of a private static variable), a one-line implementation can be used:
// In the .h file @property (nonatomic, readonly) NSString *foo; @property (nonatomic, readonly) NSInteger bar; @property (nonatomic, class, readonly) BOOL baz; @property (nonatomic, class, readonly) NSString *qux; // In the .m file static NSString *QuxValue = @""; - (NSString *)foo { return @"foo"; } - (NSInteger)bar { return 1; } + (BOOL)baz { return YES; } + (NSString *)qux { return QuxValue; }
Header Styling
-
Use class extensions to more effectively implement private methods and clean up header files.
-
Always implement
IBOutlet
s andIBAction
s in your implementation file. -
If you need access to
IBOutlet
s in a subclass make areadonly
public property in the header file. -
Use
NS_DESIGNATED_INITIALIZER
for any designated initializer methods because it works better with the future -
Know the difference between
nonatomic
andatomic
properties. Big Nerd Ranch found a performance hit in atomic properties because they lock, retain, and autorelease the objects being set. Use the modifier that fits your need. -
Annotate nullability.
- Regions that have been audited for nullability can be wrapped in
NS_ASSUME_NONNULL_BEGIN
andNS_ASSUME_NONNULL_END
, noting exceptions to the assumption withnullable
as needed. - Outside of audited regions, all object properties should be annotated with
nullable
,nonnull
,null_unspecified
, ornull_resettable
as appropriate. - All method parameter types and return types that are objects (though not
NSError **
) should be annotated withnullable
ornonnull
(immediately after the opening(
of the type specification) as appropriate. - Block parameters that are pointers should be annotated with
_Nullable
or_Nonnull
as appropriate. - See also: Swift Blog post about nullablity, NSHipster article with nullability information
- Regions that have been audited for nullability can be wrapped in
-
Property spacing:
-
Incorrect
@property(nonatomic,weak,null_unspecified)IBOutlet UITableView *tableView;
-
Correct
@property (nonatomic, weak, null_unspecified) IBOutlet UITableView *tableView;
-
-
If you have 3 or more arguments break and align at the colons.
-
Incorrect
- (void)setUserWithName:(nonnull NSString *)name height:(nullable NSNumber *)height weight:(nullable NSNumber *)weight age:(nullable NSNumber *)age;
-
Correct
- (void)setUserWithName:(nonnull NSString *)name height:(nullable NSNumber *)height weight:(nullable NSNumber *)weight age:(nullable NSNumber *)age;
-
-
In a method name, put a space after the
-
or+
and a space between the variable type and the*
-
Incorrect
-(nullable NSString*)stringForDate:(nonnull NSDate*)date;
-
Correct
- (nullable NSString *)stringForDate:(nonnull NSDate *)date;
-
-
Create vertical groups to separate IBAction methods and IBOutlet variables (grouped logically)
-
Incorrect
@property (weak, null_unspecified) IBOutlet UITableView *tableView; @property (nullable) NSArray *tableArray; @property (weak, null_unspecified) IBOutlet UITableViewCell *tableViewCell; - (nullable NSString *)stringForDate:(nonnull NSDate *)date; - (IBAction)pressedDateButton:(nullable id)sender; - (nullable NSDate *)dateForString:(nonnull NSString *)string;
-
Correct
@property (weak, null_unspecified) IBOutlet UITableView *tableView; @property (weak, null_unspecified) IBOutlet UITableViewCell *tableViewCell; @property (nullable) NSArray *tableArray; - (IBAction)pressedDateButton:(nullable id)sender; - (nullable NSString *)stringForDate:(nonnull NSDate *)date; - (nullable NSDate *)dateForString:(nonnull NSString *)string;
-
-
Break lines after each logical group.
Implementation Styling
-
Put
IBOutlet
properties in the class extension unless explicitly public. -
Only directly access instance variables in
init
,dealloc
, and custom getters/setters. Use dot notation for properties everywhere else. Among other reasons this is because properties send KVO notifications automatically. Use properties! -
Keep private methods and properties in your implementation file's class extension.
-
Example:
#import "FAKEViewController.h" @interface FAKEViewController () @property (copy, nullable) NSString *someString; @property (nullable) NSMutableDictionary *someDictionary; @property (weak, null_unspecified) IBOutlet UILabel *fakeLabel; - (void)doSomethingWithString:(nonnull NSString *)anotherString; @end @implementation FAKEViewController - (IBAction)tappedFakeButton:(id)sender { self.fakeLabel.text = self.someString; } - (void)doSomethingWithString:(NSString *)anotherString { self.someDictionary[@"someKey"] = anotherString; }
-
Method Styling
-
As of Xcode 4.4 there is no need to synthesize variables. Do not synthesize variables, let Xcode do it for you.
-
Method Declaration (Same break rules as the header for the method name. Break after the method name before the open bracket.)
-
One parameter:
- (void)setProfile:(NSDictionary *)profile { ... }
-
3 or more parameters (aligned at the colons):
- (void)setUserProfile:(NSString *)userName age:(NSString *)userAge height:(NSString *)userHeight weight:(NSString *)userWeight { ... }
-
-
Method Invocation
-
One parameters
[newProfile setProfile:profile];
-
3 or more parameters (aligned at the colons):
[newProfile setUserProfile:userName age:userAge height:userHeight];
-
Categories
Categories are a fun way to glom methods onto existing classes. To avoid naming collisions always prefix your category methods with a lowercase three letter prefix. You should use the same prefix used throughout your project. For example:
- (nonnull UIFont *)rdc_boldFontOfSize:(CGFloat)pointSize
Note: This is optional when the category is on a completely internal class since you have control over method names. (If a category method has the same selector as a method in the class, it may not be clear which implementation is called.)
Blocks
-
Whenever you create a method that takes a block parameter,
typedef
the block for readability and maintainability. -
When
typedef
ing blocks, includeBlock
,Completion
, or something similar in the type name to denote that the type is a block type. -
Be aware of how blocks capture strong references to objects used in the block, potentially creating retain cycles, know how to prevent such retain cycles using weak references, and how to create temporary strong references from such weak references.
Shoulds
Code Organization
#import
order (alphabetical within each section, blank line between sections)- The class's own header
- System Frameworks
- Libraries/Cocoapods
- Project Files: Categories
- Project Files: Controllers
- Project Files: View Controllers
- Project Files: Models
- Project Files: View Objects
- Project Files: Other
Constants
Constants accessible outside an implementation file should be declared using FOUNDATION_EXPORT
.
Example header:
FOUNDATION_EXPORT NSString *const XXXBaseURL;
Example implementation:
NSString *const XXXBaseURL = @"http://someapp-staging.herokuapp.com/api/";
-
FOUNDATION_EXPORT
is used in lieu ofextern
becauseFOUNDATION_EXPORT
will compile to a language specific designation, (extern
in C andextern "C"
in C++) making it more compatible and safer to use. -
If you're putting
static
in a header (.h) file, you're probably doing it wrong.
Variable Qualifiers
When it comes to the variable qualifiers introduced with ARC, the qualifier (__strong
, __weak
, __unsafe_unretained
, __autoreleasing
, __block
) should be placed between the asterisks and the variable name, e.g., NSString *__weak text
.
Dot Syntax
Using dot syntax for properties is a must. Dot syntax should also be used for parameter-less getter-like methods, both instance and class. For example:
[NSUserDefaults.standardUserDefaults setBool:YES forKey:@"CacheDataAggressively"];
NSManagedObjectContext *backgroundContext = VOKCoreDataManager.sharedInstance.temporaryContext;
Do not use dot notation to invoke methods that return void
.
// Do NOT do this.
VOKCoreDataManager.sharedInstance.managedObjectContext.saveMainContextAndWait;
Tips & Tricks
DRYer String Constants
C99 compilers, including Objective-C compilers, combine a run of adjacent string literals into a single string literal. This can be combined with judicious use of #define
'd strings to reduce repetition in sets of string constants, such as API paths.
Consider:
static NSString *const APIPathCurrentUserFetch = @"v1/user/";
static NSString *const APIPathFacebookLoginRegister = @"v1/user/facebook";
static NSString *const APIPathSignOut = @"v1/user/logout";
static NSString *const APIPathTerms = @"v1/text/terms";
static NSString *const APIPathPrivacy = @"v1/text/privacy";
static NSString *const APIPathNotificationRegister = @"v1/notifications/token";
static NSString *const APIPathNotificationsPreferences = @"v1/notifications/preferences";
If the v1
at the beginning of those paths ever needed to change, it'd have to be changed in every single line.
Using #define
to declare the building blocks, then put them together to make the static NSString *const
values:
#define APIPathComponentBase @"v1/"
#define APIPathComponentUser APIPathComponentBase @"user/"
static NSString *const APIPathCurrentUserFetch = APIPathComponentUser;
static NSString *const APIPathFacebookLoginRegister = APIPathComponentUser @"facebook";
static NSString *const APIPathSignOut = APIPathComponentUser @"logout";
#define APIPathComponentText APIPathComponentBase @"text/"
static NSString *const APIPathTerms = APIPathComponentText @"terms";
static NSString *const APIPathPrivacy = APIPathComponentText @"privacy";
#define APIPathComponentNotifications APIPathComponentBase @"notifications/"
static NSString *const APIPathNotificationRegister = APIPathComponentNotifications @"token";
static NSString *const APIPathNotificationsPreferences = APIPathComponentNotifications @"preferences";
Now, v1
is only in one place. Changing it there changes it for every API path.
When the preprocessor runs, the #define
'd constants are replaced with their string values so that, for example, APIPathNotificationsPreferences
becomes:
static NSString *const APIPathNotificationsPreferences = @"v1/" @"notifications/" @"preferences";
The compiler then combines the adjacent string literals, so that APIPathNotificationsPreferences
is equal to @"v1/notifications/preferences"
.
NSNull, NULL, Nil, and nil
Checking if something is nothing can be complicated. Remember that NSNull
objects are not nil
. NSNull
is a class implementing the singleton pattern; [NSNull null]
returns a singleton instance of that class. It should only be used to represent nil
objects in collections. Essentially:
NSNull *null = [NSNull null]; // An actual object, not a 0 pointer.
Class Nil = (Class)0;
id nil = (id)0;
void *NULL = (void *)0;
When comparing NULL
, Nil
, nil
, and NSNull
remember the following statements are all YES
:
[NSNull null] isEqual:[NSNull null]
[NSNull null] == [NSNull null]
[NSNull null] != nil
[NSNull null] != Nil
[NSNull null] != NULL
nil == Nil == NULL