介紹
這份規(guī)范指南概括了使用 Objective-C 時所遵循的代碼約定。
示例代碼如下:
typedef NS_ENUM(NSInteger, StyleGuideFormat) {
StyleGuideFormatLeft,
StyleGuideFormatRight,
StyleGuideFormatUp,
StyleGuideFormatDown
};
static NSString * const StyleGuideDidChangedNotification = @"StyleGuideDidChangedNotification";
static NSString * const StyleGuideUserInfoKey = @"StyleGuideUserInfoKey";
static NSString * const StyleGuideInvalidFormatException = @"StyleGuideInvalidFormatException";
static const NSInteger StyleGuideTotalCount = 100;
@interface StyleGuide () <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
@property (nonatomic, readonly, getter = isEditable) BOOL editable;
@property (nonatomic, readwrite) NSDictionary *userInfo;
@end
@implementation StyleGuide
- (void)dealloc {
[super dealloc];
}
- (instancetype)init {
self = [super init];
if (self) {
//Custom initialization
}
return self;
}
- (NSInteger)count {
NSInteger keyCount = 10;
NSInteger objectCount = 20;
NSInteger total = keyCount + objectCount;
return total;
}
@end
格式
縮進
一個縮進使用 4 個空格,不要使用制表符(tab)縮進。
空行
不同的模塊之間以空行相隔,這有助于視覺清晰度和代碼組織性,有以下幾種(示例代碼參考本指南開頭的代碼):
- @interface, @implementation, @protocol, @end, @optional, @required 與相鄰的模塊之間應該有空一行。
- 方法體實現之間應該正好空一行。
變量
- 指針類型變量 * 應緊靠變量名。
- 在ARC中擁有性限定符(__strong, __weak, __unsafe_unretained, __autoreleasing)以及 __block 應位于類型名之前。
推薦:
NSString *name;
__weak NSString *name;
__block NSString *name;
反對:
NSString* name;
NSString*name;
NSString * name;
常量
- 定義指針類型的常量時,const 位于 * 之后,且以空格相隔。
- 定義值類型的常量是, const 位于類型名之前。
推薦:
NSString * const Name = @"John";
const NSInteger TotalNum = 100;
運算符
- 單目運算符如 !, & 等與運算對象之間無空格。
- 雙目運算符與運算對象之間以空格相隔。
- 三目運算符 ? 應以 () 括起來。
推薦:NSInteger result = (isTrue ? number1 : number2);
- 如果一行出現過多的
&&
/||
而需要分行時,&&
/||
置于每行行尾。
推薦:if (self.count == another.count && self.name == another.name && self.title == another.name &&) { }
程序塊
方法的大括號和其他的大括號(if
/else
/switch
/while
等等)始終和聲明在同一行開始,在新的一行結束。
推薦:
if (user.isHappy) {
// Do something
} else {
// Do something else
}
方法頭
- 在方法簽名中,在 -/+ 符號后應該有一個空格。
- 返回值類型與方法名之間沒有空格。
推薦:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
多參數方法
當一個方法包含多個參數時,可以考慮每行一個參數,以 : 對齊。
推薦:
[NSError errorWithName:
withType:
withCode:];
當方法名中存在名字片段過長或者過短而無法以 : 對齊時,建議從第二行開始每行縮進四個空格,左邊對齊。
推薦:
[error short:
keyName:
lastLongKeyName:];
(), <>, {}, @[], @{}
-
(), <>, {} 與相鄰的模塊之間應該以空格分隔。
推薦:
if () { } else { } @interface NSObject <NSObject>
以下幾種情況除外:
- 方法聲明和定義。
推薦:
```
- (instancetype)initWithImage:(UIImage *)image;
```
- 強制類型轉換。
推薦:
```
NSInteger count = (NSInteger)5.0f;
NSString *tip = (__bridge_transfer NSString *)CFSTR("Hello World");
```
- enum 和 bitmask 定義。
推薦:
```
typedef NS_ENUM(NSUInteger, NSExpressionType)
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions)
```
-
(), <>, @[], @{} 與其包含的元素之間無空格。
推薦:
(void) id<Protocol1> ((x = y)) @[@1] array[0] @{@“key”: @"value"} dictionary[@"key"]
反對:
( void ) id< Protocol1 > ( ( x = y ) ) @[ @1 ] array[ 0 ] @{ @“key”: @"value" } dictionary[ @"key" ]
-
(), <>, @[], @{} 其內包含的元素之間以,空格相隔。
推薦:
(1, 2, 3) id<Protocol1, Protocol2> @[@1, @2, @3] @{@"k": @"v", @"m": @"n"}
反對:
(1,2,3) id<Protocol1,Protocol2> @[@1,@2,@3] @{@"k": @"v",@"m": @"n"}
-
@{} 中鍵值應以:空格分隔。
推薦:
@{@"k": @"v"}
反對:
@{@"k":@"v"}
如果@{}中包含多個鍵值對,則建議每行一個鍵值對且左對齊。
推薦:
@{
@"k": @"v",
@"m": @"n"
}
反對:
@{@"k": @"v", @"m": @"n"}
-
<> 中包含多個協(xié)議,可考慮分行。從次行開始每個協(xié)議名左對齊于第一個協(xié)議名。
推薦:
@protocol UITableViewDelegate <NSObject, UIScrollViewDelegate>
類
冒號“: ”兩邊以空格相連。如下:
推薦:
@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
屬性
按照原子性、可讀性、擁有性、以及自定義 Accesor Method 方法順序排列,默認值可省去。
推薦:
@property (nonatomic, readonly, copy, setter = setTitle) NSString *title;
命名
采用 camel-casing 命名法:將每個單詞的首字母大寫然后拼接起來。
基本原則
-
清晰
- 命名的清晰性高于簡單和簡潔,應在追求清晰的同時盡量保持簡潔。
- 盡量避免使用縮寫,即使很長也要拼寫出來。
- 一些通用的縮寫可以看情況使用。
- 避免二義性。
-
一致性
盡量保持命名在整個程序內的一致性,主要包含兩點:- 同一名字表示相同的意義。
- 同樣的概念使用同一名字表示,切忌同時使用多個名字。
前綴
- 應用程序代碼不建議使用前綴,當開發(fā)第三方使用的庫時則應該使用前綴。
- 協(xié)議名、函數名、常量名、以及枚舉名應以其所關聯的類名作為前綴。
推薦:
typedef NS_ENUM(NSInteger, UIScreenOverscanCompensation);
NSString *const UIScreenDidConnectNotification;
NSData *UIImagePNGRepresentation(UIImage *image);
@protocol UIAlertViewDelegate <NSObject>
類
類名通常應該由名詞組成,并完全遵循 camel-casing。
協(xié)議
協(xié)議名字通常根據其包含的方法而定,有以下幾類:
-
包含了一些相關的方法,通常作為一組類的接口,其命名方式為:操作 + ing。
推薦:
@protocol NSLocking - (void)lock; - (void)unlock; @end @protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end
-
包含一組相關的方法,用于在代理對象和被代理對象之間傳遞數據,其命名方式為:類名 + DataSource。
推薦:
@protocol UITableViewDataSource <NSObject>
-
包含一組相關的方法, 用于響應操作和控制程序流,其命名方式為:類名 + Delegate。
推薦:
@protocol UIAlertViewDelegate <NSObject> @optional - (void)alertView: clickedButtonAtIndex: ; - (BOOL)alertViewShouldEnableFirstOtherButton:;
變量
變量名字采用 camel-casing 命名法且首字母小寫。
-
對于數組類型的變量,其命名方式為:名詞/詞組 + s。
推薦:
NSMutableArray *gestureRecognizers;
反對:
NSMutableArray *gestureRecognizerArray;
-
對于字典類型的變量,其命名方式為:名詞/詞組 + s/Info。
推薦:
NSDictionary *fileAttributes; NSDictionary *userInfo;
反對:
NSDictionary *fileAttributeDictionary; NSDictionary *userDictionary;
實例變量遵從變量的命名方式,且以 _ 為前綴。
屬性
屬性遵從變量的命名方式,如果屬性名字是形容詞,需指定 get 訪問器。
推薦:
@property (nonatomic, readonly, getter = isPlayable) BOOL playable;
枚舉
枚舉常量的名字應以枚舉變量的名字為前綴,其命名方式為:枚舉類型 + 枚舉狀態(tài)。枚舉類型的命名方式為:類名 + 名詞/詞組。
推薦:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
通知
通知命名方式:[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
推薦:
NSSystemTimeZoneDidChangeNotification
MPMediaLibraryDidChangeNotification
UIKeyboardWillShowNotification
異常
異常命名方式:[Name of associated class] + [UniquePartOfName] + Exception。
推薦:
NSRangeException
NSInvalidArgumentException
鍵
鍵指鍵值對中的鍵,用在字典中。其命名方式:[Name of associated class] + [UniquePartOfName] + Key。
推薦:
UIKeyboardFrameBeginUserInfoKey
ALAssetLibraryUpdatedAssetsKey
方法
方法命名遵從變量的命名方式。
-
對于表示動作的方法,以動詞開頭。
不要使用 do 或者 does 等很少有意義的語氣助詞。另外,不要在動詞前使用副詞或形容詞。
-
對于返回對象屬性的方法,建議直接使用屬性作為方法名。
反對使用 get 作為前綴,或者其他的動詞,即使該方法并非直接返回結果,而是需要一些運算。存在多個返回值時,則
推薦:
- (NSSize)cellSize;
反對:
- (NSSize)calcCellSize; - (NSSize)getCellSize;
-
在所有參數前都要有關鍵字。
推薦:
- (void)sendAction:(SEL)aSelector toObject:(id)anObject;
反對:
- (void)sendAction:(SEL)aSelector :(id)anObject;
-
描述參數的詞緊靠在參數之前。
推薦:
- (id)viewWithTag:(NSInteger)aTag;
反對:
- (id)taggedView:(int) aTag;
-
針對 Cocoa 或 第三方庫中的類所添加的類別中的方法,需為方法名添加前綴,基于項目名的縮寫構成前綴,形如
TT_
。對于派生自他們的子類,如果擔心添加的方法可能會跟基類中得方法名沖突,通用建議使用前綴方法名。考慮這樣一個場景,在8.0發(fā)布之前你為 NSString 添加了一個類別,其中包含一個這樣的方法 *containsString:(NSString )aString 。待到8.0發(fā)布時,蘋果不幸地意識到確實需要一個這樣的方法,在 NSString 中新增了同樣的一個接口,這絕對是災難性地!
其他
注釋
當需要的時候,注釋應該被用來解釋 為什么 特定代碼做了某些事情。所使用的任何注釋必須保持最新否則就刪除掉。
通常應該避免一大塊注釋,代碼就應該盡量作為自身的文檔。
Warning的要求
除了第三方庫,不能引入新的警告
類前置聲明
通常引用一個類有兩種辦法:一種是通過#import方式引入;另一種是通過@class引入;在頭文件中通過@class引入這個類作為一個類型使用,減少頭文件的重復包,提升編譯效率。在實現文件中,如果需要引用到被引用類的實體變量或者方法時,還需要使用#import方式引入被引用類。
頭文件聲明:
@class StyleGuide;
StyleGuide *styleGuide;
實現文件引入頭文件:
#import @"StyleGuilde.h"
#pragma mark分組
一些類(尤其是一些控制器類)可能很長,方法和函數彈出菜單可以便于代碼導航。此時加入#pragma 指令對代碼進行邏輯組織很有效果。提高可讀性
例如:
#pragma mark - Initialization
私有方法屬性定義
外部不需使用的屬性方法成員變量不能暴露到.h文件。在類的.m文件中,采用類別來實現私有方法
例如:.m文件中定義
@interface MyClass()
- (void)privateMethod;
@end
小貼士
點“.”語法
應該 始終 使用點“.”語法來訪問或者修改屬性,除此之外,不得使用點“.”語法。
推薦:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
反對:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
條件判斷
條件判斷主體部分應該始終使用大括號括住來防止[出錯][Condiationals_1],即使它可以不用大括號(例如它只需要一行)。這些錯誤包括添加第二行(代碼)并希望它是 if 語句的一部分時。還有另外一種[更危險的][Condiationals_2],當 if 語句里面的一行被注釋掉,下一行就會在不經意間成為了這個 if 語句的一部分。此外,這種風格也更符合所有其他的條件判斷,因此也更容易檢查。
推薦:
if (isFinished) {
return success;
}
反對:
if (isFinished)
return success;
if (isFinished) return success;
三目運算符
三目運算符,? ,只有當它可以增加代碼清晰度或整潔時才使用。單一的條件都應該優(yōu)先考慮使用,多條件時通常使用 if 語句會更易懂。
推薦:
result = (a > b ? x : y);
反對:
result = (a > b ? x = c > d ? c : d : y);
錯誤處理
當引用一個返回錯誤參數(error parameter)的方法時,應該針對返回值,而非錯誤變量。一些蘋果的 API 在成功的情況下會寫一些垃圾值給錯誤參數(如果非空),所以針對錯誤變量可能會造成虛假結果。
推薦:
NSError *error;
if (![self trySomethingWithError:&error]) {
// 處理錯誤
}
反對:
NSError *error;
[self trySomethingWithError:&error];
if (error != nil) {
// 處理錯誤
}
init 和 dealloc
dealloc
方法應該放在@implementation
的最上面,并且剛好在 @synthesize
和 @dynamic
語句的后面。在任何類中,init
都應該直接放在 dealloc
方法的下面。
init
方法的結構應該像這樣:
- (instancetype)init {
self = [super init];
if (self != nil) {
// Custom initialization
}
return self;
}
字面量
每當創(chuàng)建 NSString
, NSDictionary
, NSArray
,和 NSNumber
類的不可變實例時,應使用 @"", @{}, @[], @()
方式生成實例。要注意 nil
值不能傳給 NSArray
和 NSDictionary
字面量,這樣做會導致崩潰。
推薦:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
反對:
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];
CGRect 函數
當訪問一個 CGRect
的 x
, y
, width
, height
時,應該使用[CGGeometry
函數][CGRect-Functions_1]代替直接訪問結構體成員。蘋果的 CGGeometry
參考中說到:
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.
推薦:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
反對:
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;
宏
應該盡量避免使用宏定義,除非沒有別的選擇,通常應當使用常量和枚舉,它們具有更強的類型檢查,而更加安全。
推薦:
static const CGFloat ThumbnailHeight = 50.0f;
反對:
#define ThumbnailHeight 2.0f
枚舉類型
當使用 enum
時,建議使用新的基礎類型規(guī)范,因為它具有更強的類型檢查和代碼補全功能。現在 SDK 包含了一個宏來鼓勵使用使用新的基礎類型 - NS_ENUM()
推薦:
typedef NS_ENUM(NSInteger, UIImageResizingMode) {
UIImageResizingModeTile,
UIImageResizingModeStretch,
};
位掩碼
當用到位掩碼時,使用 NS_OPTIONS
宏。
舉例:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
布爾
typedef signed char BOOL;
#define YES ((BOOL)1)
#define NO ((BOOL)0)
以上代碼片斷來自objc.h
。永遠不將布爾變量直接和 YES
進行比較,因為 YES
被定義為 1,而 BOOL
可以多達 8 位。
if (isAwesome)
if (![someObject boolValue])
反對:
if ([someObject boolValue] == NO)
if (isAwesome == YES)
單例
單例對象應該使用線程安全的模式創(chuàng)建共享的實例。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
通用的縮寫
Abbreviation | Meaning and comments |
---|---|
alloc | Allocate. |
alt | Alternate. |
app | Application. |
calc | Calculate. |
dealloc | Deallocate. |
func | Function. |
horiz | Horizontal. |
info | Information. |
init | Initialize. |
int | Integer. |
max | Maximum. |
min | Minimum. |
msg | Message. |
nib | Interface Builder archive. |
pboard | Pasteboard (but only in constants). |
rect | Rectangle. |
Rep | Representation. |
temp | Temporary. |
vert | Vertical. |
以下是一些在計算機領域比較知名的縮寫:
ASCII
XML
HTML
URL
RTF
HTTP
TIFF
JPG
PNG
GIF
LZW
ROM
RGB
CMYK
MIDI
FTP