一、前言
因為公司團隊需要,為公司寫了 iOS 開發規范,過程中自己也學習到了很多。
為了讓代碼更加整潔,團隊配合更加順暢,制定開發規范文檔。本規范文檔結合 Apple's Guide、Google's OC Guide ,以及行業內比較知名的編碼規范和開發團隊的經驗總結而制定,例如 Github's Guide、Raywenderlich's Guide ,有不完善和不合理的地方后期會持續修改增刪。
二、項目目錄
|- AppDelegate // 整個應用的入口
|- Category // AppDelegate的擴展:3DTouch、OpenURL、RemoteNotification...
|- StartLanch // App啟動后業務功能操作,使用類方法進行一些操作
|- SDKManager // AppDelegate中需要做的第三方SDK的配置
|- EHIVendorSDKDefines.h // SDK的key/appID相關數據定義
|- EHIVendorSDKManager // 多個SDK的配置
|- EHIPushManager
...
|- Defines // 整個應用會用到的定義
|- EHIMacro.h // 必用頭文件引入(YYKit.h)
|- EHIAppDefines.h // App信息、UI的宏定義(字體、顏色、適配)
|- EHIFunctionDefines // 全局方法或內斂函數定義(EHIWeakSelf/EHIStrongSelf)
|- EHIEventDefines // 統計事件使用的Key
|- Features // 功能模塊目錄
|- Load // 主頁加載之前的引導頁
|- Login
|- UserCenter
|- Home
|- SelfDriving
|- EHISelfDrivingDefines // 枚舉、提示語、數據存儲Key、通知(每個模塊都根據需要定義)
|- Step1
|- Step2
|- Step3
|- Step4
...
|- Chauffeur
|- OrderCenter
...
|- Tools // 工具類、Categories
|- Foundation
|- NSArray // Categories
|- UIButton
|- Category // Categories
|- UIKit // UI類的多了個自定義控件文件夾
|- UI // 除了系統類的自定義UI
|- UploadPhotosView
...
|- SDKManager // 對第三方SDK的封裝(分享、加載圖片...)
|- Manager // 非第三方SDK的功能封裝
|- AuthorityManager
...
|- Helpers // 幫助類。包含網絡、數據庫、定位等操作類的封裝和實現
|- NetWork
|- Data
|- Location
...
|- Vendors // 第三方的類庫/SDK。部分需要修改或者不支持cocoapod的第三方的框架引入
|- Resources // 資源文件。包含圖片、聲音、文件(.plist/.json/.ttc)
|- GIFImages
|- ProjectCer
...
|- Supporting Files // Xcode自帶文件夾
|- Images.xcassets // 工程默認圖片存放文件夾,可以在該下按不同模塊創建文件夾存放對應的圖片
三、代碼格式
3.1 語言
應該使用 US 英語。
應該:
UIColor *myColor = [UIColor whiteColor];
不應該:
UIColor *myColour = [UIColor whiteColor];
3.2 空格和換行
- 使用
Tab
符號,而非空格。首行縮進一個 Tab ,即 4 個空格; - 使用換行符結束一個文件;
- 方法大括號和其他大括號(
if
/else
/switch
/while
/for
等)總是在同一行語句打開但在新行中關閉; - 合理的使用空格將代碼劃分為邏輯塊,例如
if
語句、for
循環、三元操作符。
應該:
for (int i = 0; i < 10; i++) {
}
不應該:
for(int i=0;i<10;i ++){
}
- 在方法之間應該有且只有一行,這樣有利于在視覺上更清晰和更易于組織。在方法內的空白應該分離功能,但通常都抽離出來成為一個新方法;
- 優先使用 auto-synthesis 。但如果有必要,
@synthesize
和@dynamic
應該在實現中每個都聲明新的一行。
3.3 每一行的最大長度
過長的一行代碼將會導致可讀性問題,一些規范建議為 80 。
一些常用的如 UITableView 的方法長度就大于 80 ,建議注釋上強制要求,方法上不強制。
可以在 Xcode > Preferences > Text Editing > Page guide at column 中將最大行長設置為 80 。
3.4 Case語句
- 大括號在
case
語句中并不是必須的,除非編譯器強制要求; - 當一個
case
語句包含多行代碼時,大括號應該加上; -
break
放在大括號}
外。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
}
break;
case 3:
// ...
break;
default:
// ...
break;
}
有很多次,當相同代碼被多個 cases 使用時,一個 fall-through
應該被使用。一個 fall-through
就是在 case
最后移除 break
語句,這樣就能夠允許執行流程跳轉到下一個 case
值。為了代碼更加清晰,一個 fall-through 需要注釋一下。
switch (condition) {
case 1:
// ** fall-through! **
case 2:
// code executed for values 1 and 2
break;
default:
// ...
break;
}
當在 switch 使用枚舉類型時,default
是不需要的。例如:
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;
switch (menuType) {
case RWTLeftMenuTopItemMain:
// ...
break;
case RWTLeftMenuTopItemShows:
// ...
break;
case RWTLeftMenuTopItemSchedule:
// ...
break;
}
3.5 布爾值
- Objective-C 使用
YES
和NO
。因為true
和false
應該只在 CoreFoundation,C 或 C++ 代碼使用; - 既然
nil
解析成NO
,所以沒有必要在條件語句比較; - 不要拿某樣東西直接與
YES
比較,因為YES
被定義為 1,而一個BOOL
能被設置為 8 位。
這是為了在不同文件保持一致性和在視覺上更加簡潔而考慮。
應該:
if (someObject) {}
if (![anotherObject boolValue]) {}
不應該:
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.
如果 BOOL
屬性的名字是一個形容詞,屬性就能忽略“ is ”綴,但要指定 get 訪問器的慣用名稱。例如:
@(工作日常)property (nonatomic, assign, getter=isEditable) BOOL editable;
文字和例子從這里引用 Cocoa Naming Guidelines 。
3.6 條件語句
條件語句主體為了防止出錯應該使用大括號包圍,即使條件語句主體能夠不用大括號編寫(如,只用一行代碼)。
這些錯誤包括添加第二行代碼和期望它成為 if
語句。例如 Single statement if block - braces or no? 中可能發生在 if
語句里面一行代碼被注釋了,然后下一行代碼不知不覺地成為 if
語句的一部分。
除此之外,這種風格與其他條件語句的風格保持一致,所以更加容易閱讀。
應該:
if (!error) {
return success;
}
不應該:
if (!error)
return success;
或
if (!error) return success;
3.6.1 三元操作符
- 當需要提高代碼的清晰性和簡潔性時,三元操作符
?:
才會使用。單個條件求值常常需要它; - 多個條件求值時,如果使用
if
語句或重構成實例變量時,代碼會更加易讀; - 一般來說,最好使用三元操作符是在根據條件來賦值的情況下。
Non-boolean
的變量與某東西比較,加上括號 ()
會提高可讀性。如果被比較的變量是 boolean
類型,那么就不需要括號。
應該:
NSInteger value = 5;
result = (value != 0) ? x : y;
BOOL isHorizontal = YES;
result = isHorizontal ? x : y;
不應該:
result = a > b ? x = c > d ? c : d : y;
3.7 Init 方法和類構造方法(instancetype)
當初始化方法和類構造方法被使用時,它應該返回類型是 instancetype
而不是 id
。這樣確保編譯器正確地推斷結果類型。
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end
關于更多 instancetype
信息,請查看 NSHipster.com 。
3.8 CGRect函數
當訪問 CGRect
里的 x
/y
/width
/height
時,應該使用 CGGeometry
函數而不是直接通過結構體來訪問。引用 Apple 的 CGGeometry :
在這個參考文檔中所有的函數,接受
CGRect
結構體作為輸入,在計算它們結果時隱式地標準化這些rectangles
。因此,你的應用程序應該避免直接訪問和修改保存在CGRect
數據結構中的數據。相反,使用這些函數來操縱rectangles
和獲取它們的特性。
應該:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);
不應該:
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;
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
3.9 黃金路徑(使用 return)
當使用條件語句編碼時,不要嵌套 if
語句,多個返回語句更 OK 。
應該:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
// Do something important
}
不應該:
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
3.10 錯誤處理
當方法通過引用來返回一個錯誤參數,判斷返回值而不是錯誤變量。
應該:
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
不應該:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}
在成功的情況下,有些 Apple 的 APIs 記錄垃圾值(garbage values)到錯誤參數(如果 non-NULL),那么判斷錯誤值會導致 false 負值和 crash 。
3.11 Block
根據 block
的長度,有不同的書寫規則:
- 較短的 block 可以寫在一行內(不強制);
- 如果分行顯示的話,block 的右括號
}
應該和調用 block 那行代碼的第一個非空字符對齊; - block 內的代碼采用 4 個空格的縮進;
- 如果 block 過于龐大,應該單獨聲明成一個變量來使用;
-
^
和(
之間,^
和{
之間都沒有空格,參數列表的右括號)
和{
之間有一個空格。
// 較短的block可以寫在一行內
[operation setCompletionBlock:^{ [self onOperationDone]; }];
// 分行書寫的block,內部使用4空格縮進
[operation setCompletionBlock:^{
[self.delegate newDataAvailable];
}];
// 使用C語言API調用的block遵循同樣的書寫規則
dispatch_async(_fileIOQueue, ^{
NSString *path = [self sessionFilePath];
if (path) {
// …
}
});
// 較長的block關鍵字可以縮進后在新行書寫,注意block的右括號`}`和調用block那行代碼的第一個非空字符對齊
// (Xcode本身對齊規則就是這樣,平時很少注意到)
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// 較長的block參數列表同樣可以縮進后在新行書寫
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
if (window) {
[self windowDidLoad:window];
} else {
[self errorLoadingWindow];
}
}];
// 龐大的block應該單獨定義成變量使用
void (^largeBlock)(void) = ^{
// …
};
[_operationQueue addOperationWithBlock:largeBlock];
// 在一個調用中使用多個block,注意到他們不是像函數那樣通過`:`對齊的,而是同時進行了4個空格的縮進
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) {
// …
}
secondBlock:^(Bar *b) {
// …
}];
另外要注意的是:
block 使用的時候判斷一下,再回調,防止未實現,導致 crash。
if (completion) {
completion(true);
}
3.12 數據結構的語法糖
應該使用可讀性更好的語法糖來構造 NSArray
,NSDictionary
等數據結構,避免使用冗長的 alloc
、init
方法。
例子:
NSArray *array = @[[foo description], [bar description]];
NSDictionary *dict = @{NSForegroundColorAttributeName: [NSColor redColor]};
如果構造代碼不寫在一行內,右括號 ]
或者 }
寫在新的一行,語句短的和長的縮進如下:
// 短的
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];
// 長的
NSDictionary *dictionary = @{
NSFontAttributeName : [UIFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
構造字典時,字典的 Key
和 Value
與中間的冒號 :
都要留有一個空格,多行書寫時,也可以將冒號 :
對齊:
// 正確,冒號`:`前后留有一個空格
NSDictionary *option1 = @{
NSFontAttributeName : [UIFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
// 正確,按照冒號`:`來對齊
NSDictionary *option2 = @{
NSFontAttributeName : [NSFont fontWithName:@"Arial" size:12],
NSForegroundColorAttributeName : fontColor
};
//錯誤,冒號前應該有一個空格
NSDictionary *wrong = @{
AKey: @"b",
BLongerKey: @"c",
};
//錯誤,每一個元素要么單獨成為一行,要么全部寫在一行內
NSDictionary *alsoWrong = @{AKey : @"a",
BLongerKey : @"b"};
四、命名規范
4.1 基本原則
4.1.1 清晰性
命名應該盡可能的清晰和簡潔,但在 Objective-C 中,清晰比簡潔更重要。由于 Xcode 強大的自動補全功能,我們不必擔心名稱過長的問題。
// 清晰
insertObject:atIndex:
// 不清晰,insert的對象類型和at的位置屬性沒有說明
insert:at:
// 清晰
removeObjectAtIndex:
// 不清晰,remove的對象類型沒有說明,參數的作用沒有說明
remove:
不要使用單詞的簡寫,拼寫出完整的單詞:
// 清晰
destinationSelection
setBackgroundColor:
// 不清晰,不要使用簡寫
destSel
setBkgdColor:
然而,有部分單詞簡寫在 Objective-C 編碼過程中是非常常用的,以至于成為了一種規范,這些簡寫可以在代碼中直接使用,下面列舉了部分:
alloc == Allocate max == Maximum
alt == Alternate min == Minimum
app == Application msg == Message
calc == Calculate nib == Interface Builder archive
dealloc == Deallocate init == Initialize
func == Function rect == Rectangle
horiz == Horizontal vert == Vertical
info == Information temp == Temporary
命名方法或者函數時要避免歧義:
// 有歧義,是返回sendPort還是send一個Port?
sendPort
// 有歧義,是返回一個名字屬性的值還是display一個name的動作?
displayName
4.1.2 一致性
整個工程的命名風格要保持一致性。
比如不同 Model 中表示性別應該用一樣的名字,比如我們需要表示性別,不可以有的用 gender ,有的用 sex 。
或者不同類中完成相似功能的方法應該叫一樣的名字,比如我們總是用 count 來返回集合的個數,不能在 A 類中使用 count 而在 B 類中使用 getNumber 。
4.2 使用前綴
前綴由大寫的字母縮寫組成,比如 Cocoa 中 NS 前綴代表 Founation 框架中的類, IB 則代表 Interface Builder 框架。
可以在為類、協議、函數、常量以及 typedef 宏命名的時候使用前綴,但注意不要為成員變量或者方法使用前綴,因為他們本身就包含在類的命名空間內。
命名前綴的時候不要和蘋果 SDK 框架沖突。
4.3 圖片資源的命名
規則如下:
- 用英文命名,不用拼音;
- 每一部分用下劃線分隔;
- 圖片名中兩倍圖在名字最后要加
@2x
,三倍圖在名字最后要加@3x
。
格式如下:
邏輯分類 _ 表現內容 _ 圖片狀態
某個模塊用到的,以模塊作為第一部分,例子:
tabbar_chat // tabbar聊天模塊正常狀態切圖
tabbar_chat_selected // tabbar聊天模塊選中狀態切圖
chat_switchCamera // 聊天模塊中拍攝界面的切換攝像頭切圖
有的切圖是整個 App 用到的,例子:
public_back // 返回切圖
public_close // 關閉切圖
4.4 類和協議( Class & Protocol ) 的命名
類名以大寫字母開頭,應該包含一個名詞來表示它代表的對象類型如 Model 。
同時可以加上統一的前綴,比如 NSString、NSArray 等等以 NS 開頭。 我們項目使用 EHI
。
而協議名稱應該清晰地表示它所執行的行為,而且要和類名區別開來,所以通常使用 ing
詞尾來命名一個協議,比如 NSCopying、NSLocking 。
4.5 委托(Delegate)的命名
當特定的事件發生時,對象會觸發它注冊的委托方法。委托是 Objective-C 中常用的傳遞消息的方式。委托有它固定的命名范式。
委托的命名參考類名,通常使用 Delegate
詞尾,比如 UITableViewDelegate、UIScrollViewDelegate。
一個委托方法的第一個參數是觸發它的對象,第一個關鍵詞是觸發對象的類名,除非委托方法只有一個名為 sender 的參數:
// 第一個關鍵詞為觸發委托的類名
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
// 當只有一個"sender"參數時可以省略類名
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
根據委托方法觸發的時機和目的,使用 should , will , did 等關鍵詞:
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
- (BOOL)windowShouldClose:(id)sender;
4.6 頭文件的命名(Headers)
源碼的頭文件名應該清晰地暗示它的功能和包含的內容:
如果頭文件內只定義了單個類或者協議,直接用類名或者協議名來命名頭文件,比如
NSLocale.h
定義了NSLocale
類。如果頭文件內定義了一系列的類、協議、類別,使用其中最主要的類名來命名頭文件,比如
NSString.h
定義了NSString
和NSMutableString
。每一個 Framework 都應該有一個和框架同名的頭文件,包含了框架中所有公共類頭文件的引用,比如
Foundation.h
。Framework 中有時候會實現在別的框架中類的類別擴展,這樣的文件通常使用
被擴展的框架名+Additions
的方式來命名,比如NSBundleAdditions.h
。
4.7 方法的命名
在方法簽名中,應該在方法類型(-
/+
符號)之后有一個空格。在方法各個段之間應該也有一個空格(符合Apple的風格)。在參數之前應該包含一個具有描述性的關鍵字來描述參數。
方法一般以小寫字母打頭,每一個后續的單詞首字母大寫,方法名中不應該有標點符號(包括下劃線),有兩個例外:
- 可以用一些通用的大寫字母縮寫打頭方法,比如 PDF、TIFF 等。
- 可以用帶下劃線的前綴來命名私有方法或者類別中的方法(如 MJRefresh )。
如果方法表示讓對象執行一個動作,使用動詞打頭來命名,注意不要使用 do,does 這種多余的關鍵字,動詞本身的暗示就足夠了:
// 動詞打頭的方法表示讓對象執行一個動作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
如果方法是為了獲取對象的一個屬性值,直接用屬性名稱來命名這個方法,注意不要添加 get 或者其他的動詞前綴:
// 正確,使用屬性名來命名方法
- (NSSize)cellSize;
// 錯誤,添加了多余的動詞前綴
- (NSSize)calcCellSize;
- (NSSize)getCellSize;
對于有多個參數的方法,務必在每一個參數前都添加關鍵詞,關鍵詞應當清晰說明參數的作用。
and
這個詞的用法應該保留。它不應該用于多個參數來說明,就像 initWithWidth:height
以下這個例子:
應該:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不應該:
-(void)setT:(NSString *)text i:(UIImage *)image; // 關鍵詞不清晰
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; // 無關鍵詞
- (id)taggedView:(NSInteger)tag; // 關鍵詞的作用不清晰
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
方法的參數命名也有一些需要注意的地方:
- 和方法名類似,參數的第一個字母小寫,后面的每一個單詞首字母大寫。
- 不要再方法名中使用類似
pointer
、ptr
這樣的字眼去表示指針,參數本身的類型足以說明。 - 不要使用只有一兩個字母的參數名。
- 不要使用簡寫,拼出完整的單詞。
下面列舉了一些常用參數名:
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString
4.8 存取方法(Accessor Methods)的命名
存取方法是指用來獲取和設置類屬性值的方法,屬性的不同類型,對應著不同的存取方法規范:
// 屬性是一個名詞時的存取方法范式
- (type)noun;
- (void)setNoun:(type)aNoun;
// 例子
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;
// 屬性是一個形容詞時存取方法的范式
- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;
// 例子
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;
// 屬性是一個動詞時存取方法的范式
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
// 例子
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag;
命名存取方法時不要將動詞轉化為被動形式來使用:
// 正確
- (void)setAcceptsGlyphInfo:(BOOL)flag;
- (BOOL)acceptsGlyphInfo;
// 錯誤,不要使用動詞的被動形式
- (void)setGlyphInfoAccepted:(BOOL)flag;
- (BOOL)glyphInfoAccepted;
可以使用 can
、should
、will
等詞來協助表達存取方法的意思,但不要使用 do
和 does
:
// 正確
- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;
// 錯誤,不要使用`do`或者`does`
- (void)setDoesAcceptGlyphInfo:(BOOL)flag;
- (BOOL)doesAcceptGlyphInfo;
為什么 Objective-C 中不適用 get
前綴來表示屬性獲取方法?因為 get 在 Objective-C 中通常只用來表示從函數指針返回值的函數:
// 三個參數都是作為函數的返回值來使用的,這樣的函數名可以使用`get`前綴
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;
4.9 集合操作類方法(Collection Methods)的命名
有些對象管理著一系列其它對象或者元素的集合,需要使用類似增刪查改
的方法來對集合進行操作,這些方法的命名范式一般為:
// 集合操作范式
- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;
// 例子
- (void)addLayoutManager:(NSLayoutManager *)obj;
- (void)removeLayoutManager:(NSLayoutManager *)obj;
- (NSArray *)layoutManagers;
注意,如果返回的集合是無序的,使用 NSSet
來代替 NSArray
。如果需要將元素插入到特定的位置,使用類似于這樣的命名:
- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index;
- (void)removeLayoutManagerAtIndex:(int)index;
如果管理的集合元素中有指向管理對象的指針,要設置成 weak
類型以防止引用循環。
下面是 SDK 中 NSWindow
類的集合操作方法:
- (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
- (void)removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *)parentWindow;
- (void)setParentWindow:(NSWindow *)window;
4.10 函數(Functions)的命名
函數的命名和方法有一些不同,主要是:
- 函數名稱一般帶有縮寫前綴,表示方法所在的框架。
- 前綴后的單詞以“駝峰”表示法顯示,第一個單詞首字母大寫。
函數名的第一個單詞通常是一個動詞,表示方法執行的操作:
NSHighlightRect
NSDeallocateObject
如果函數返回其參數的某個屬性,省略動詞:
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect aRect)
如果函數通過指針參數來返回值,需要在函數名中使用Get:
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
函數的返回類型是BOOL時的命名:
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
4.11 屬性的命名
所有屬性特性應該顯式地列出來,因為一般都是用 nonatomic
,但是默認是 atomic
且不頻繁使用,所以為了看著整齊,把 nonatomic
寫在前面。
屬性和對象的存取方法相關聯,屬性的第一個字母小寫,后續單詞首字母大寫,不必添加前綴。屬性按功能命名成名詞或者動詞:
// 名詞屬性
@property (nonatomic, strong) NSString *title;
// 動詞屬性
@property (nonatomic, assign) BOOL showsAlpha;
屬性也可以命名成形容詞,這時候通常會指定一個帶有 is
前綴的 get
方法來提高可讀性:
@property (nonatomic, assign, getter=isEditable) BOOL editable;
命名實例變量,在變量名前加上 “_”
前綴(有些有歷史的代碼會將 “_”
放在后面),其它和命名屬性一樣:
@implementation MyClass {
BOOL _showsTitle;
}
當使用屬性時,實例變量應該使用點語法 self.
來訪問和改變。當你使用點語法時,通過使用 getter 或 setter 方法,屬性仍然被訪問或修改。想了解更多,閱讀這里。
但有一個特例:在初始化方法里,實例變量(例如: _variableName )應該直接被使用來避免 getters/setters 潛在的副作用。
局部變量不應該包含下劃線。
4.12 對象的命名
對象的命名都統一格式如下:
名稱 + 類名
應該:
UIButton *settingsButton;
不應該:
UIButton *setBut;
4.13 通知(Notifications)的命名
通知常用于在模塊間傳遞消息,所以通知要盡可能地表示出發生的事件。
但是請慎用,例如一個頁面 FirstViewController 出另一個 SecondViewController ,SecondViewController 給 FirstViewController 傳值用 Block 即可。
通知的命名范式是:
k + [觸發通知的類名] + [Did | Will(狀態)] + [動作] + Notification
例如系統的都以 NS 開頭:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
4.14 常量、宏、枚舉的命名
如果要定義一組相關的常量,盡量使用枚舉類型,枚舉類型的命名規則和函數的命名規則相同。 建議使用 NS_ENUM
和 NS_OPTIONS
宏來定義枚舉類型:
// 定義一個枚舉
typedef NS_ENUM(NSInteger, NSMatrixMode) {
NSRadioModeMatrix,
NSHighlightModeMatrix,
NSListModeMatrix,
NSTrackModeMatrix
};
使用匿名枚舉定義 bit map :
typedef NS_OPTIONS(NSUInteger, NSWindowMask) {
NSBorderlessWindowMask = 0,
NSTitledWindowMask = 1 << 0,
NSClosableWindowMask = 1 << 1,
NSMiniaturizableWindowMask = 1 << 2,
NSResizableWindowMask = 1 << 3
};
使用 const
定義浮點型或者單個的整數型常量,如果要定義一組相關的整數常量,應該優先使用枚舉。常量的命名規范和函數相同:
static NSString * const EHITableViewCellReuseIdentifier = @"TableViewCellReuseIdentifier";
不要使用 #define
宏來定義常量,如果是整型常量,盡量使用枚舉,浮點型常量,使用 const
定義。#define
統一格式要加前綴 k
:
#define kUIColorHex(_hex_) [UIColor colorWithHexString:((__bridge NSString *)CFSTR(#_hex_))]
五、注釋
5.1 文件注釋
不強制性要求,如果需要,寫的清晰易懂即可。這里拿 AFNetWorking 的框架來做模板:
// AFHTTPRequestOperation.h
//
// Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
5.2 方法和屬性注釋
在 .h
文件中采用 Apple 的標準注釋風格,好處是可以在引用的地方 alt + 點擊
自動彈出注釋,在寫代碼調用時也有提示方法或屬性的說明,非常方便。
有很多可以自動生成注釋格式的插件,推薦使用 VVDocumenter:
在 .m
文件中,這種方式參數多的時候占行數多也太啰嗦,可以用如下良好的方式說明即可:
/** Stop current preconnecting when application is going to background */
- (void)stopRunning {
}
協議、委托的注釋要明確說明其被觸發的條件:
/** Delegate - Sent when failed to init connection, like p2p failed. /
- (void)initConnectionDidFailed:(IPCConnectHandler *)handler {
}
在方法實現中使用 //
注釋即可,但是后面必須追加一個空格分隔后面的注釋語句。
如果在注釋中要引用參數名或者方法函數名,使用一對 “ ` ” 將參數或者方法括起來以避免歧義:
// Sometimes we need `count` to be less than zero.
// Remember to call `StringWithoutSpaces(“foo bar baz”)`.
5.3 段注釋
#pragma mark - 段索引
該語句上下各保留一個換行,這個是一個分割線的效果,便于多個方法分類查看。
六、編碼風格
6.1 關于如何在代碼塊中留回車
這里拿的是 AFNetworking 框架做的一個說明:
6.2 點符號語法
點語法是一種很方便封裝訪問方法調用的方式。當你使用點語法時,通過使用 getter
或 setter
方法,屬性仍然被訪問或修改。想了解更多,閱讀 這里 。
點語法應該總是被用來訪問和修改屬性,因為它使代碼更加簡潔。[]
符號更偏向于用在其他例子。
應該:
NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
不應該:
NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
6.3 nil、Nil、Null、NSNull 的使用
我們先來看下這些空值的定義:
nil: Defines the id of a null instance,指向一個(實例)對象的空指針
例如:
NSString *msg = nil;
NSDate *date =nil;
Nil: Defines the id of a null class,指向一個類的空指針
例如:
Class class = Nil;
NULL:定義其他類型(基本類型、C類型)的空指針
char *p = NULL;
NSNull:數組中元素的占位符,數據中的元素不能為nil(可以為空,也就是NSNull),
原因:nil 是數組的結束標志
如果用nil,就會變成
NSArray *array = [NSArray arrayWithObjects:
[[NSObject alloc] init],
nil,
[[NSObject alloc] init],
[[NSObject alloc] init],
nil];,
那么數組到第二個位置就會結束。打印[array count]的話會顯示1而不是5
6.4 “==”判斷
如果常量值在表達式右側,代碼將會編譯和執行,并不像預期的那樣。
如果常量值在表達式左側,這個錯誤將在第一次嘗試編譯時被捕獲。
應該:
if ( constant == var )
if ( NULL == pointer )
不應該:
if ( var == constant )
if ( pointer == NULL )
6.5 Getter 方法
Getter
方法全部寫在文件所有方法之后,按照聲明的順序來寫,用段注釋和其他方法分隔:
#pragma mark - Getter
書寫上建議統一使用下面這種方式,易復用。
應該:
- (UIButton *)loginButton {
if (!_loginButton) {
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:@"登錄" forState:UIControlStateNormal];
button.backgroundColor = [UIColor cyanColor];
button.layer.cornerRadius = 4;
button.clipsToBounds = YES;
[self.view addSubview:button];
_loginButton = button;
}
return _loginButton;
}
不應該:
- (UIButton *)loginButton {
if (!_loginButton) {
_loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_loginButton setTitle:@"登錄" forState:UIControlStateNormal];
_loginButton.backgroundColor = [UIColor cyanColor];
_loginButton.layer.cornerRadius = 4;
_loginButton.clipsToBounds = YES;
[self.view addSubview:_loginButton];
}
return _loginButton;
}
6.6 屬性特性的使用(copy/strong/retain)
strong
和 retain
:兩者基本是一樣的,在 ARC 環境下統一使用 strong。不一樣的是 ARC 下的 strong 修飾 block 的時候會對 block 進行 copy 操作,不過還是建議保留好習慣,block 務必用 copy 修飾。
copy
:用于 NSString、NSArray、NSDictionary 等有對應的可變類型的屬性,和 Block (具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks)。
copy 特性:
- 因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
- 如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
6.7 屬性特性的使用 (weak/assign)
assign
:修飾基本數據類型和結構體。
weak
:修飾指針變量。例如 delegate ,一定要用 weak 修飾。
weak 比 assign 多了一個功能就是當屬性所指向的對象消失的時候(也就是內存引用計數為0)會自動賦值為 nil ,這樣再向 weak 修飾的屬性發送消息就不會導致野指針操作 crash 。
6.8 Block 和 Delegate 的使用
Block 和 Delegate 回調之前要判斷。
例如:
// Block
if (completionBlock) {
completionBlock(operations);
}
// Delegate
if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
[delegate performSelector:@selector(hudWasHidden:) withObject:self];
}
6.9 .h 文件的書寫
屬性和方法都盡量寫在 .m 文件中,不要輕易暴露,外部其他文件需要使用的時候才寫在 .h 文件中。
6.10 擴展Category
文件命名格式如下:
類名 + 擴展名
UIImage+Extension
不要濫用擴展,需要擴展前檢查下是否已有類似類,例如 UIImage 已有 UIImage+Extension ,包含了圖片裁剪和方向調整,就不用另寫 UIImage+Resize 擴展了,如果想添加方法,在原有擴展上加。
當然功能比較獨立情況除外,例如 YYKit 中:
UIImage+YYAdd // 圖片處理,例如裁剪、高斯模糊、圓角等
UIImageView+YYWebImage // 加載web圖片
6.11 能用新的不用老的,比如YYKit
盡量使用新的技術。比如由于項目比較久比較龐大,以前都用 MJRefresh 的一些封裝,后來有了 YYKit 對一些類的封裝更好,新的代碼使用 YYKit ,慢慢以舊代新。
七、工具推薦
7.1 Alcatraz
7.1.1 Xcode8 兼容
Xcode8 開始蘋果禁止使用插件,所以要做一次 XCode8 上的兼容,親測有效:
安裝 Alcatraz 時老是報錯的話,直接從GitHub上下載 Alcatraz 工程 -> 打開 Alcatraz 工程 -> 運行 Alcatraz 工程 -> 退出 Xcode -> 重新打開 Xcode ->
Load Bundle
。
7.1.2 Xcode 中看不到 Package Manager
當時 OK 了,后來再打開 Xcode -> Window 又看不到 Package Manager
了,一番折騰后找到一篇文章,簡單解決掉了。。。文章如下:
7.1.3 插件安裝無效
終于能打開 Package Manager 方便的安裝插件啦。但是安裝后退出 Xcode ,再打開沒有彈框是否 Load Bundles ,插件確實使用不了。解決如下:
在終端輸入命令使用 update_xcode_plugins
更新插件后,再重新打開 Xcode 就會彈框是否 Load Bundles 了,選擇 Load ,就可以使用了。
update_xcode_plugins 的安裝和使用參考如下:
7.2 好用插件推薦
以下是我用過覺得好用的插件推薦,大家還有什么好的插件的話歡迎推薦!
7.2.1 FuzzyAutocomplete
FuzzyAutocomplete 通過添加模糊匹配來提高 Xcode 代碼自動補全功能,開發者無需遵循從頭匹配的原則,只要記得方法里某個關鍵字即可進行匹配,很好地提高了工作效率。
7.2.2 KSImageNamed
KSImageNamed 為項目中使用的 UIImage 的 imageNamed: 方法提供文件名自動補全功能。使用 [UIImage imageNamed:@"xxx"] 時,該插件會掃描整個 workspace 中的圖片文件,顯示圖片信息。
7.2.3 OMColorSense
OMColorSense 。代碼里的那些冷冰冰的顏色數值,到底時什么顏色?如果你經常遇到這個問題,每每不得不運行下代碼去看看,那么這個插件絕對不容錯過。更彪悍的是你甚至可以點擊顯示的顏色面板,直接通過系統的 ColorPicker 來自動生成對應顏色代碼!
使用:任意一個顏色,點擊顏色行,該行的右上角會出現色快,點擊這個色塊可以選擇顏色。
7.2.4 AutoHighlightSymbol
AutoHighlightSymbol 當你在 Xcode 選中一個詞或者搜查一個詞的時候標注高亮顏色,比 Xcode 自身的虛下劃線明顯得多。
7.2.5 VVDocumenter
VVDocumenter 規范化代碼注釋,只需要輸入三個斜線 ///
,就自動顯示規范注釋了。
7.2.6 FKRealGroup
FKRealGroup 是一個增強Xcode創建、刪除文件夾的插件。FKRealGroup會在編輯菜單中添加 New Real Group
和 Delete Real Group
兩個選項。
Xcode本身的 New Group
選項只會創建一個虛擬文件夾,并不會在本地磁盤創建真實文件夾。New Real Group
選項會在相應磁盤目錄創建一個真實的文件夾,Delete Real Group
同理。
7.2.7 Auto-Importer
Auto-Importer 快速導入頭文件。
使用方法:選中你要導入的頭文件的名稱,使用快捷鍵 ? + ctrl + H
即可。
7.2.8 DXXcodeConsoleUnicodePlugin
DXXcodeConsoleUnicodePlugin 轉換 Xcode 控制臺中 Unicode 為中文漢字。
使用方法(當然推薦使用方法2)
快捷鍵
option + c
會轉換當前剪貼板中的內容并用一個對話框把轉換后的內容顯示出來;在
Xcode
的Edit
菜單中勾選ConvertUnicodeInConsole
,然后 console 中再出現 Unicode 碼時,就會自動轉換成與顯示。