iOS Guidelines
Project Structure
Organized .xcodeproj: files are grouped by layers (Presentation, Domain/Model, Infrastructure, etc), responsibilities (UI, Model, Logic, Vendor, etc), roles, etc. No flat structure allowed
Organized project folder: desirable but not required to keep file structure in sync with .xcodeproj
One .m / .swift == One class. No extra classes allowed
Localization is required even for the case, when you have only one language. Therefore adding extra language won’t cause any difficulty in future
Images and other resources (.plist) should be grouped as well (i.e. $SRCROOT/Resources/Images/Common/, $SRCROOT/Resource/Assets/)
File Structure (.m)
- Structure
imports
related constants
class extension
@implementation
- dealloc
- init methods
- other
@end
- Method sections should be separated by #pragma mark – $sectionName
Braces, Asterisk
Curly braces are opened on the same line with code, closed – on a new line. For short blocks / closures a single line can be used
Asterisk placed near the name
Type *var = ...;
Every code block should be wrapped by
{}
:
if (statement) {
NSLog(@"%@", var);
}
The only exception is a short statement and an immediate return in the beginning:
- (void)performSomeTask {
if (!user.hasToken) return;
...
}
Spaces, Formatting
General
Tab consists of 4 spaces
Max symbols in a row: 120
Use space after
if
,while
,for
and similar:
if (pointer != someOtherPointer)
CGFloat result = width * height * 2.f;
BOOL contentExists = self.content.length > 0;
for (int i = 0; i < x; i++) { ... }
Alignment: do not align code as ASCII-art.
Separate logical code blocks with 1 line wrap. Random number of \n is not allowed
Variable declaration
- Between Type & Protocol there are no spaces:
id<NSObject> object = ...;
- Between var and casting parentheses there are no spaces:
ClassType *a = (ClassType *)b;
,
ScalarType a = (ScalarType)b;
Class Declaration
- If listing protocols goes beyond 120 characters, place each on a separate line:
@interface SCHCategoryViewController : UIViewController <UITableViewDelegate>
@interface SCHCategoryViewController : UIViewController <
UITableViewDelegate,
UITableViewDataSource,
SCHSyncronizationDelegate
>
Method Declaration
- Use a single space between +/- and a returned type. Any additional spaces in arguments list are not allowed:
- (void)doSomethingWithString:(NSString *)string flag:(BOOL)flag;
- If the method goes beyond 120+10 characters, place each argument on a separate line, align by colon:
- (void)doSomethingWithString:(NSString *)string
rect:(CGRect)rectangle
length:(NSUInteger)length;
Method Invocation
- Method invocation should use the same formatting as for declaration
Function Declaration
No space symbol between name and arguments parentheses
If the method goes beyond 120+10 characters, parentheses should be placed the same way, as opening/closing braces. Place each argument on a separate line aligned by 1 tab:
void * LongLongLongFunction(
id firstArgument,
id secondArgument,
id *outArgument,
int other
)
NSAssert(
[controller conformsToProtocol:@protocol(EParticipantSelection)],
@"Controller %@ should confrom to protocol",
NSStringFromProtocol(@protocol(EParticipantSelection))
);
Access specifiers & ivars
-
@public
,@private
,@protected
should be aligned by 1 tab:
@interface MyClass : NSObject {
@private
id _privateIvar;
@protected
id _protectedIvar;
}
- Access specifiers should be declared explicitly
Property Declaration
Between
@property
and opening parentheses 1 space symbol should be usedParameters ordering: atomic/nonatomic, memory policy, access specified (readonly/readwrite), setter declaration, getter declaration:
@property (nonatomic, copy, readonly, getter=customGetter) NSString *value;
Protocol Declaration
-
@required
and@optional
aligned to the line beginning
Blocks / Closures Declaration
Code inside block / closure aligned by 1 tab
Opening and closing braces should be aligned by the first symbol of declaration:
dipatch_async(dispatch_get_main_queue(), ^{
// your code here
});
NSArray *contentArray = nil;
[contentArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
// your code here
}];
[UIView animateWithDuration:0.3 animations:^{
// your code here
} completion:^(BOOL finished){
// your code here
}];
- There is no space between
^
and return type / arguments:^(id response){}
^NSUInteger * (id response){}
Naming
In general we’re using Apple Coding Guidelines for Cocoa with several highlights:
Three-letters prefix is REQUIRED
Methods for the non-project class categories should be prefixed by
lowercasePrefix_
:
- (void)sch_performTask:(id)arg
Constants
Magical numbers are not allowed – constants should be used instead. Exception: constant value, used in local one place with descriptive name
No “raw” strings. Exception: the same as for the magical numbers
Desirable to use
extern
keyword:
// SCHNotifier.h
OBJC_EXTERN NSString * const SCHNotifierDidChangeStateNotification;
OBJC_EXTERN const CGFloat SCHDefaultAnimationDuration;
// SCHNotifier.m
NSString * const SCHNotifierDidChangeStateNotification = @"com.project.notifier.stateDidChange";
const CGFloat SCHDefaultAnimationDuration = 0.33;
Required / recommended best practices
Types Declaration / Usage
Use typedef for blocks declaration and custom enums / scalar types
Use NSInteger, NSTimeInterval, CGFloat, etc over plain types. Therefore it is
easier to migrate to the 64-bits
Forward Declaration
Use forward declaration whenever possible
Ivars
@public
ivars not allowed
Properties
Use
copy
specifier in case mutability leads to unexpected behaviour (i.e. when NSMutableString passed as NSString and mutated outside of the class)Use
readonly
for public properties whenever possible to prevent unexpected class usage (i.e. outlet assigned from client code, etc)(iOS only) Use
nonatomic
whenever possible to speedup code executionDesirable to access underlying property’s ivar only in init/setter/getter
Use of @property solely to create _ivar is not recommended and should be prohibited
Exceptions handling
Use exceptions only where it is required. Desirable to use NSError ** and / or return result status (i.e. Success, Fail)
Protocols
- Delegate methods should always pass
sender
argument:
@protocol CustomClassDelegate <NSObject>
- (NSInteger)someDelegateMethod:(CustomClass *)customClass;
- (void)customClass:(CustomClass *)customClass didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
@end
- Protocol support for properties / vars should be declared explicitly:
id<Protocol> instance = ...;
, @property (nonatomic, weak) id<Protocol> delegate;
Boolean statements
- Desirable to use implicit equality check:
if (bar && baz && quux)
Preprocessor usage
- Prefer c-functions / constants / methods over #define
AppDelegate
Keep your AppDelegate as clean as possible. Any logic, not related to AppDelegate (database seeding, networking, etc) is not allowed
Clean .pch
Group required #defines, constants to a separate header (SCHConstants.h, SCHDefines.h). Garbage in .pch is not allowed