前言
Objective-C語言主要用于iOS和Mac開發(fā)找田,本文結合蘋果規(guī)范和Google規(guī)范杭措,再加上自己的個人經驗總結出來的一系列編碼規(guī)范垂蜗。
蘋果 Objective-C規(guī)范:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html
Google Objective-C規(guī)范:
http://zh-google-styleguide.readthedocs.org/en/latest/google-objc-styleguide/
代碼縮進和行代碼
關于代碼縮進Apple和Google有兩種規(guī)范:Apple是以4個空格縮進喘先,而Google以2個空格縮進钳吟。在此我們采用Apple的規(guī)范即4個空格縮進,但是不能使用TAB制表符窘拯,所以Xcode里面我們這樣設置(見圖1)红且,即以TAB代表4個空格坝茎,一次編寫代碼時候縮進的時候就用TAB鍵而不必自己輸入空格。
關于每行代碼最大長度我們選用100個字符作為最大長度限制暇番,過長的代碼會導致可讀性問題嗤放,可以在Xcode設置(見圖2),編寫的代碼長度不可以大于這個最大值壁酬。
文件定義
頭文件引入
1.當引入Objective-C或者Objective-C++頭文件的時候使用#import
引入次酌,當引入C或者C++頭文件的時候使用#include
引入
2.引入系統(tǒng)API時應該直接引用這個根頭文件,而不是其它子模塊的頭文件舆乔,即使是你只用到了其中的一小部分岳服,編譯器會自動完成優(yōu)化的。每一個框架都會有一個和框架同名的頭文件希俩,它包含了框架內接口的所有引用吊宋。例如:
//正確做法:
#import <UIKit/UIKit.h>
//錯誤做法
#import <UIKit/UIButton.h>
3.引入系統(tǒng)的API時使用#import <>
格式
4.引入第三方框架優(yōu)先使用#import <>
格式
5.引入自己定義的類使用#import ""
格式
6.將 import和其他的文件名之間加一個空格。如果有一個以上的 import 語句颜武,就對這些語句進行分組璃搜。每個分組的注釋是可選的。
注:對于模塊使用 #import語法鳞上。除了子類化或是協(xié)議之外这吻,最好使用 @class 這種方式,避免過多的頭文件引入篙议。在引入協(xié)議的時候唾糯,如果不是連當前類也引入的情況下,將協(xié)議單獨聲明出來再引入涡上。
命名規(guī)范
基本原則
所有的命名必須是英文規(guī)范趾断,命名應該盡量的清晰和簡潔拒名。在Objective-C中吩愧,命名清晰重要程度要比簡潔更重。
命名通知名稱等一些key時必須用英文!
// 正確
NSString *kLogoutSuccessNotification = @"LogoutSuccessNotification";
// 錯誤
NSString *kLogoutSuccessNotification = @"退出登錄";
所有修飾符例如=, +, -, &, |
兩邊必須留有空格
// 清晰
insertObject:atIndex:
// 不清晰增显, insert 的對象類型和 at 的位置屬性沒有說明
insert:at:
但是有一些單詞簡寫在 Objective-C 編碼過程中是非常常用的,例如:
alloc == Allocate
init == Initialize
max == Maximum
min == Minimum
alt == Alternate
app == Application
msg == Message
calc == Calculate
nib == Interface Builder archive
dealloc == Deallocate
pboard == Pasteboard
func == Function
rect == Rectangle
horiz == Horizontal
vert == Vertical
Rep == Representation
info == Information
temp == Temporary
一致性
整個工程的命名風格要保持一致性雁佳,最好和蘋果 SDK 的代碼保持統(tǒng)一。不同類中完成相似功能的方法應該叫一樣的名字同云,比如我們總是用 count 來返回集合的個數(shù)糖权,不能在 A 類中使用 count 而在 B 類中使用 getNumber
前綴
Apple規(guī)定:Apple保留所有兩個字母前綴的使用權,因此為了防止與官方API沖突炸站,項目中所有文件的前綴必須是三個(及以上)大寫字母星澳。
可以在為類、協(xié)議旱易、函數(shù)禁偎、常量以及 typedef 宏命名的時候使用前綴腿堤,但注意不要為成員變量或者方法使用前綴,因為他們本身就包含在類的命名空間內如暖。
命名屬性或實例變量(property)
定義一個屬性時笆檀,編譯器會自動生成線程安全的存取方法( Atomic ),但這樣會大大降低性能盒至,特別是對于那些需要頻繁存取的屬性來說酗洒,是極大的浪費。所以如果定義的屬性不需要線程保護枷遂,記得手動添加屬性關鍵字 nonatomic
來取消編譯器的優(yōu)化樱衷。
變量名應該盡可能命名為描述性的。除了 for 循環(huán)外酒唉,其他情況都應該避免使用單字母的變量名箫老。 星號表示指針屬性變量,例如: NSString text 不要寫成 NSString text 或者NSString * text 黔州,常量除外耍鬓。
盡量定義屬性來代替直接使用實例變量,同時聲明內存的管理方式。如果一個屬性只在 init 方法里設置了一次流妻,聲明為 readonly 牲蜀。
格式如下:
@property (nonatomic, strong) ClassName *name;
注意:
1.在 @property
后有一個空格
2.修飾符之間用, + 空格
隔開
3.類名與“)”之間有一個空格
4.如果是對象類型,屬性名與*
在一起并與類名之間隔開一個空格
定義NSString和Block類型時绅这,使用copy涣达。
定義基本數(shù)據(jù)類型時,使用assign
定義代理對象時证薇,使用weak度苔, 格式
@property(nonatomic, weak) id <UIAlertViewDelegate> delegate;
定義對象類型時使用strong
定義BOOL型屬性時,應該表明getter方法
// 錯誤
@property (nonatomic, assign) BOOL isShow;
// 正確
@property (nonatomic, assign, getter=isShow) BOOL show;
在定義變量時有個兩種方式:
第一種:
// 正確做法
{
NSData *_data;
}
// 錯誤做法
{
NSData *data;
}
第二種:
@property (nonatomic, strong) NSData *data;
建議采取第二種方式浑度,因為第二種方式系統(tǒng)會默認生成getter和setter方法
命名方法( Methods )
Objective-C 的方法名通常都比較長寇窑,這是為了讓程序有更好地可讀性,按蘋果的說法 “ 好的方法名應當可以以一個句子的形式朗讀出來 ” 箩张。
在方法簽名中甩骏,在 -/+ 符號后應該有一個空格。方法片段之間也應該有一個空格先慷。構造方法使instancetype作為返回類型來代替 id饮笛。
對于私有方法,應該加前綴用以區(qū)分论熙。具體使用可以自行決定福青,建議使用p加下劃線的方式:p_
, p表示"private",不建議使用單個下劃線的方式,這種方式是預留給蘋果使用的无午。
方法一般以小寫字母打頭二蓝,每一個后續(xù)的單詞首字母大寫,方法名中不應該有標點符號(包括下劃線)指厌,有兩個例外:
可以用一些通用的大寫字母縮寫打頭方法刊愚,比如 PDF,TIFF 等。
可以用帶下劃線的前綴來命名私有方法或者類別中的方法踩验。
如果方法表示讓對象執(zhí)行一個動作鸥诽,使用動詞打頭來命名,注意不要使用 do 箕憾, does 這種多余的關鍵字牡借,動詞本身的暗示就足夠了:
// 動詞打頭的方法表示讓對象執(zhí)行一個動作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
如果方法是為了獲取對象的一個屬性值,直接用屬性名稱來命名這個方法袭异,注意不要添加 get 或者其他的動詞前綴:
// 正確钠龙,使用屬性名來命名方法
- (NSSize)cellSize;
// 錯誤,添加了多余的動詞前綴
- (NSSize)calcCellSize;
- (NSSize)getCellSize;
對于有多個參數(shù)的方法御铃,務必在每一個參數(shù)前都添加關鍵詞碴里,關鍵詞應當清晰說明參數(shù)的作用:
// 正確,保證每個參數(shù)都有關鍵詞修飾
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag;
// 錯誤上真,遺漏關鍵詞
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
// 正確
- (id)viewWithTag:(NSInteger)aTag;
// 錯誤咬腋,關鍵詞的作用不清晰
- (id)taggedView:(int)aTag;
不要用 and 來連接兩個參數(shù),通常 and 用來表示方法執(zhí)行了兩個相對獨立的操作(從設計上來說睡互,這時候應該拆分成兩個獨立的方法):
// 錯誤根竿,不要使用 "and" 來連接參數(shù)
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
// 正確,使用 "and" 來表示兩個相對獨立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
方法的參數(shù)命名也有一些需要注意的地方 :
* 和方法名類似就珠,參數(shù)的第一個字母小寫寇壳,后面的每一個單詞首字母大寫
不要再方法名中使用類似 pointer,ptr 這樣的字眼去表示指針,參數(shù)本身的類型足以說明
* 不要使用只有一兩個字母的參數(shù)名
* 不要使用簡寫妻怎,拼出完整的單詞
下面列舉了一些常用參數(shù)名:
...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...target:(id)anObject
命名基本數(shù)據(jù)類型
為了兼容32-bit和64-bit 當定義基本數(shù)據(jù)類型的時候應該采用蘋果SDK提供的數(shù)據(jù)類型
int -> NSInteger
unsigned -> NSUInteger
float -> CGFloat
double -> CGFloat
動畫時間 -> NSTimeInterval
…
命名代理( Delegate )
當特定的事件發(fā)生時壳炎,對象會觸發(fā)它注冊的代理方法。代理是 Objective-C 中常用的傳遞消息的方式蹂季。代理有它固定的命名范式冕广。
一個代理方法的第一個參數(shù)是觸發(fā)它的對象疏日,第一個關鍵詞是觸發(fā)對象的類名偿洁,除非代理方法只有一個名為 sender 的參數(shù):
// 第一個關鍵詞為觸發(fā)代理的類名
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
// 當只有一個 "sender" 參數(shù)時可以省略類名
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
// 根據(jù)代理方法觸發(fā)的時機和目的,使用 should,will,did 等關鍵詞
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window; 沟优、
- (BOOL)windowShouldClose:(id)sender;
命名常量(Constants)
不要使用 #define
宏來定義常量涕滋, #define
通常用來給編譯器決定是否編譯某塊代碼,比如常用的:
#ifdef DEBUG
使用 const
定義基本數(shù)據(jù)類型或字符串常量挠阁,常量的命名規(guī)范和函數(shù)相同,但是常量一般用k:
對于本類私有的的定義方式如下:
static const CGFloat xxxx
static NSString *const title
如果定義公用的常量方式如下:
.h文件: extern NSString *const title;
.m文件: NSString *const title = @"title";
命名通知( Notifications )
通知常用于在模塊間傳遞消息宾肺,所以通知要盡可能地表示出發(fā)生的事件溯饵,通知的命名范式是:
[ 觸發(fā)通知的類名 ] + [Did | Will] + [ 動作 ] + Notification 例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
代碼規(guī)范
不要使用 new 方法
盡管很多時候能用 new 代替 alloc init 方法,但這可能會導致調試內存時出現(xiàn)不可預料的問題锨用。 Cocoa 的規(guī)范就是使用 alloc init 方法丰刊,使用 new 會讓一些讀者困惑。
新語法使用
在Objective-C新語法中增拥,.h聲明類中要在@interface
和@end
上下分別加上NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
并留有空格啄巧, 格式如下:
NS_ASSUME_NONNULL_BEGIN
@interface APPTextView : UIView
@end
NS_ASSUME_NONNULL_END
NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
默認會對屬性和方法參數(shù)及返回值增加nonnull
,如果確定某個屬性或者方法參數(shù)可以為空掌栅,請加上修飾符nullable
BOOL 的使用
BOOL 在 Objective-C 中被定義為 signed char 類型秩仆,這意味著一個 BOOL 類型的變量不僅僅可以表示 YES(1) 和 NO(0) 兩個值,所以永遠不要將 BOOL 類型變量直接和 YES 比較:
// 錯誤猾封,無法確定 |great| 的值是否是 YES(1) 澄耍,不要將 BOOL 值直接與 YES 比較
BOOL great = [foo isGreat];
if (great == YES)
// ...be great!
// 正確
BOOL great = [foo isGreat];
if (great)
// ...be great!
同樣的,也不要將其它類型的值作為 BOOL 來返回晌缘,這種情況下齐莲, BOOL 變量只會取值的最后一個字節(jié)來賦值,這樣很可能會取到 0 ( NO )磷箕。但是铅搓,一些邏輯操作符比如 &&,||,! 的返回是可以直接賦給 BOOL 的:
// 錯誤,不要將其它類型轉化為 BOOL 返回
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}
// 正確
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
// 正確搀捷,邏輯操作符可以直接轉化為 BOOL
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
邏輯判斷的使用
關于邏輯判斷一般就是if else 判斷星掰,在if else 判斷中,必須加上{ }
,無論是判斷語句是一句 還是多句嫩舟。判斷應該先篩除不符合的邏輯氢烘,然后處理重點邏輯。如果判斷的執(zhí)行邏輯是return
可以不寫{ }
家厌,但是return必須與判斷語句在一行播玖,防止出錯。
例如:
if (判斷語句) return;
更多情況下應該是
if (判斷) {
// 邏輯代碼
} else {
// 邏輯代碼
}
不要留有沒有執(zhí)行邏輯的判斷
// 錯誤
if (i == 3) {
} else{
// 執(zhí)行邏輯
}
//正確
if (i != 3) {
// 執(zhí)行邏輯
}
在 init 和 dealloc 中不要用存取方法訪問實例變量
當 init dealloc 方法被執(zhí)行時饭于,類的運行時環(huán)境不是處于正常狀態(tài)的蜀踏,使用存取方法訪問變量可能會導致不可預料的結果,因此應當在這兩個方法內直接訪問實例變量掰吕。
// 正確果覆,直接訪問實例變量
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init];
}
return self;
}
- (void)dealloc {
[_bar release];
[super dealloc];
}
// 錯誤,不要通過存取方法訪問
- (instancetype)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string];
}
return self;
}
- (void)dealloc {
self.bar = nil;
[super dealloc];
}
保證 NSString 在賦值時被復制
NSString 非常常用殖熟,在它被傳遞或者賦值時應當保證是以復制( copy )的方式進行的局待,這樣可以防止在不知情的情況下 String 的值被其它對象修改。
- (void)setTitle:(NSString *)aTitle {
_title = [aTitle copy];
}
nil 檢查
因為在 Objective-C 中向 nil 對象發(fā)送命令是不會拋出異常或者導致崩潰的钳榨,只是完全的 “ 什么都不干 ” 舰罚,所以,只在程序中使用 nil 來做邏輯上的檢查薛耻。
另外营罢,不要使用諸如 nil == Object 或者 Object == nil 的形式來判斷。
// 正確饼齿,直接判斷
if (!objc) {
...
}
// 錯誤愤钾,不要使用 nil == Object 的形式
if (nil == objc) {
...
}
命名枚舉
項目中強烈建議用枚舉替代魔法數(shù)字
項目中強烈建議用枚舉替代魔法數(shù)字
項目中強烈建議用枚舉替代魔法數(shù)字
(重要的事情說三遍)并且枚舉項的注釋也得完善
枚舉類型優(yōu)先采用Objective-C語法定義,格式如下:
/// 枚舉注釋
typedef NS_ENUM (NSInteger, QZGProfile) {
QZGProfileIDCard = 0, //!< 枚舉項注釋
};
凡是需要按位或操作來組合的枚舉都應該使用NS_OPTIONS定義候醒。
/// 枚舉注釋
typedef NS_OPTIONS(NSUInteger, OKState) {
OKState1 = 1 << 0,
OKState2 = 1 << 1
};
魔法數(shù)字
項目中必須盡可能的避免魔法數(shù)字的出現(xiàn),如果是一組數(shù)據(jù),應當采用枚舉方式.
如果用到tag,建議采用char類型定義,因為字符類型會對應ASCII表中的數(shù)字,比如'A','b'等,但是最多設置情況為四個字符,比如'ABCD'.
其他
不要在對象類型前和 protocol之間添加空格能颁。
// 推薦:
@property (nonatomic, weak) id<OKDelegate> delegate;
//反對:
@property (nonatomic, weak) id <OKDelegate> delegate;
Xcode 工程
為了避免文件雜亂,物理文件應該保持和 Xcode 項目文件同步倒淫。Xcode 創(chuàng)建的任何組(group)都必須在文件系統(tǒng)有相應的映射伙菊。為了更清晰,代碼不僅應該按照類型進行分組敌土,也可以根據(jù)功能進行分組镜硕。
注釋
好的代碼應該是 “ 自解釋 ” ( self-documenting )的,但仍然需要詳細的注釋來說明參數(shù)的意義返干、返回值兴枯、功能以及可能的副作用。方法矩欠、函數(shù)财剖、類、協(xié)議癌淮、類別的定義都需要注釋躺坟,推薦采用 Apple 的標準注釋風格,好處是可以在引用的地方 alt+ 點擊自動彈出注釋乳蓄,非常方便咪橙。
推薦使用 VVDocumenter
文件注釋
文件注釋放在.h文件的頭部,格式為:
/// @brief 對類的功能進行說明
/// @since 從哪個APP版本號開始使用
/// @author 最近更改代碼的人
屬性注釋
1.如果注釋內容過多使用格式:
/*
* 很長很長的注釋內容
* 很長很長的注釋內容
*/
@property (nonatomic, strong) ClassName *name;
2.當注釋內容不多時虚倒,注釋使用///
或//!<
格式:
/// 注釋內容
@property (nonatomic, strong) ClassName *name;
或者
@property (nonatomic, strong) ClassName *name; //!< 注釋內容
方法注釋
1.方法注釋在.h生命類里面應該用
/**
* 方法功能說明
*
* @param 參數(shù)說明
*
* @return 返回值說明
*/
或者
/// 方法功能說明
///
/// @param 參數(shù)說明
///
/// @return 返回值說明
建議用VVDocumenter添加注釋美侦, 如果方法注釋只有一行內容可以用///
注釋
2.方法注釋在.m實現(xiàn)類里面應該用 #pragma mark - 注釋內容
或 #pragma mark 注釋內容
警告與錯誤
關于警告與錯誤我專門分出來寫,因為我認為應該對警告和錯誤有足夠的重視魂奥。關于錯誤這個可以說的不多菠剩,如果編寫的代碼出錯了,Xcode也會看不下去的捧弃,因此Xcode會給你一個修改提示赠叼。
對于警告擦囊,優(yōu)秀的程序員提交的最終代碼應該是不包含任何警告的违霞,因此在編寫的時候一旦出現(xiàn)警告嘴办,我們應該盡力去解決。如果實在避免不了可以用
#pragma clang diagnostic push
#pragma clang diagnostic ignored "警告類型"
//要忽略的警告內容
#pragma clang diagnostic pop
這種方式來解除警告买鸽,但是這種方式不可濫用涧郊,除非你非常確定這種警告對于項目的傷害微乎其微。
總結
無規(guī)矩不成方圓眼五,愿這篇文章能夠幫助到大家妆艘。本人會不定時更新,如有異議看幼,請issue我批旺。