Objective-C 編碼規范,內容來自蘋果、谷歌的文檔翻譯,自己的編碼經驗和對其它資料的總結。
Objective-C 是一門面向對象的動態編程語言,主要用於編寫 iOS 和 Mac 應用程序。關於 Objective-C 的編碼規范,蘋果和谷歌都已經有很好的總結:
Apple Coding Guidelines for Cocoa
Google Objective-C Style Guide
本文主要整合了對上述文檔的翻譯、作者自己的編程經驗和其他的相關資料,為公司總結出一份通用的編碼規范。
Objective-C中@property的所有屬性詳解 http://www.linuxidc.com/Linux/2014-03/97744.htm
Objective-C 和 Core Foundation 對象相互轉換的內存管理總結 http://www.linuxidc.com/Linux/2014-03/97626.htm
使用 Objective-C 一年後我對它的看法 http://www.linuxidc.com/Linux/2013-12/94309.htm
10個Objective-C基礎面試題,iOS面試必備 http://www.linuxidc.com/Linux/2013-07/87393.htm
Objective-C適用C數學函數 <math.h> http://www.linuxidc.com/Linux/2013-06/86215.htm
好學的 Objective-C 高清PDF http://www.linuxidc.com/Linux/2014-09/106226.htm
不要在工程裡使用 Tab 鍵,使用空格來進行縮進。在 Xcode > Preferences > Text Editing 將 Tab 和自動縮進都設置為 4 個空格。(Google 的標准是使用兩個空格來縮進,但這裡還是推薦使用 Xcode 默認的設置。)
同樣的,在 Xcode > Preferences > Text Editing > Page guide at column: 中將最大行長設置為 80 ,過長的一行代碼將會導致可讀性問題。
一個典型的 Objective-C 函數應該是這樣的:
在 - 和 (void) 之間應該有一個空格,第一個大括號 { 的位置在函數所在行的末尾,同樣應該有一個空格。(我司的 C 語言規范要求是第一個大括號單獨占一行,但考慮到 OC 較長的函數名和蘋果 SDK 代碼的風格,還是將大括號放在行末。)
如果一個函數有特別多的參數或者名稱很長,應該將其按照 : 來對齊分行顯示:
在分行時,如果第一段名稱過短,後續名稱可以以 Tab 的長度( 4 個空格)為單位進行縮進:
函數調用
函數調用的格式和書寫差不多,可以按照函數的長短來選擇寫在一行或者分成多行:
以下寫法是錯誤的:
@public 和 @private 標記符
@public 和 @private 標記符應該以 一個空格 來進行縮進:
協議( Protocols )
在書寫協議的時候注意用<> 括起來的協議和類型名之間是沒有空格的,比如 IPCConnectHandler() , 這個規則適用所有書寫協議的地方,包括函數聲明、類聲明、實例變量等等:
閉包( Blocks )
根據 block 的長度,有不同的書寫規則:
較短的 block 可以寫在一行內。
如果分行顯示的話, block 的右括號 } 應該和調用 block 那行代碼的第一個非空字符對齊。
block 內的代碼采用 4 個空格 的縮進。
如果 block 過於龐大,應該單獨聲明成一個變量來使用。
^ 和 ( 之間, ^ 和 { 之間都沒有空格,參數列表的右括號 ) 和 { 之間有一個空格。
應該使用可讀性更好的語法糖來構造 NSArray , NSDictionary 等數據結構,避免使用冗長的 alloc,init 方法。
如果構造代碼寫在一行,需要在括號兩端留有一個空格,使得被構造的元素於與構造語法區分開來:
如果構造代碼不寫在一行內,構造元素需要使用 兩個空格 來進行縮進,右括號 ] 或者 } 寫在新的一行,並且與調用語法糖那行代碼的第一個非空字符對齊:
構造字典時,字典的 Key 和 Value 與中間的冒號 : 都要留有一個空格,多行書寫時,也可以將 Value 對齊:
命名規范
命名應該盡可能的清晰和簡潔,但在 Objective-C 中,清晰比簡潔更重要。由於 Xcode 強大的自動補全功能,我們不必擔心名稱過長的問題。
不要使用單詞的簡寫,拼寫出完整的單詞:
然而,有部分單詞簡寫在 Objective-C 編碼過程中是非常常用的,以至於成為了一種規范,這些簡寫可以在代碼中直接使用,下面列舉了部分:
命名方法或者函數時要避免歧義
整個工程的命名風格要保持一致性,最好和蘋果 SDK 的代碼保持統一。不同類中完成相似功能的方法應該叫一樣的名字,比如我們總是用 count 來返回集合的個數,不能在 A 類中使用 count 而在 B 類中使用 getNumber 。
如果代碼需要打包成 Framework 給別的工程使用,或者工程項目非常龐大,需要拆分成不同的模塊,使用命名前綴是非常有用的。
前綴由大寫的字母縮寫組成,比如 Cocoa 中 NS 前綴代表 Founation 框架中的類, IB 則代表 Interface Builder 框架。
可以在為類、協議、函數、常量以及 typedef 宏命名的時候使用前綴,但注意 不要為成員變量或者方法使用前綴,因為他們本身就包含在類的命名空間內。
命名前綴的時候不要和蘋果 SDK 框架沖突。
命名類和協議( Class&Protocol )
類名以大寫字母開頭,應該包含一個 名詞 來表示它代表的對象類型,同時可以加上必要的前綴,比如 NSString,NSDate,NSScanner,NSApplication 等等。
而協議名稱應該清晰地表示它所執行的行為,而且要和類名區別開來,所以通常使用 ing 詞尾來命名一個協議,比如 NSCopying,NSLocking 。
有些協議本身包含了很多不���關的功能,主要用來為某一特定類服務,這時候可以直接用類名來命名這個協議,比如 NSObject 協議,它包含了 id 對象在生存周期內的一系列方法。
命名頭文件( Headers )
源碼的頭文件名應該清晰地暗示它的功能和包含的內容:
如果頭文件內只定義了單個類或者協議,直接用類名或者協議名來命名頭文件,比如 NSLocale.h 定義了 NSLocale 類。
如果頭文件內定義了一系列的類、協議、類別,使用其中最主要的類名來命名頭文件,比如 NSString.h 定義了 NSString 和 NSMutableString 。
每一個 Framework 都應該有一個和框架同名的頭文件,包含了框架中所有公共類頭文件的引用,比如 Foundation.h
Framework 中有時候會實現在別的框架中類的類別擴展,這樣的文件通常使用被擴展的框架名 +Additions 的方式來命名,比如 NSBundleAdditions.h 。
命名方法( Methods )
Objective-C 的方法名通常都比較長,這是為了讓程序有更好地可讀性,按蘋果的說法 “ 好的方法名應當可以以一個句子的形式朗讀出來 ” 。
方法一般以小寫字母打頭,每一個後續的單詞首字母大寫,方法名中不應該有標點符號( 包括下劃線 ),有兩個例外:
可以用一些通用的大寫字母縮寫打頭方法,比如 PDF,TIFF 等。
可以用帶下劃線的前綴來命名私有方法或者類別中的方法。
如果方法表示讓對象執行一個動作,使用動詞打頭來命名,注意不要使用 do , does 這種多余的關鍵字,動詞本身的暗示就足夠了:
如果方法是為了獲取對象的一個屬性值,直接用屬性名稱來命名這個方法,注意不要添加 get 或者其他的動詞前綴:
對於有多個參數的方法,務必在每一個參數前都添加關鍵詞,關鍵詞應當清晰說明參數的作用:
不要用 and 來連接兩個參數,通常 and 用來表示方法執行了兩個相對獨立的操作(從設計上來說,這時候應該拆分成兩個獨立的方法):
方法的參數命名也有一些需要注意的地方 :
和方法名類似,參數的第一個字母小寫,後面的每一個單詞首字母大寫
不要再方法名中使用類似 pointer,ptr 這樣的字眼去表示指針,參數本身的類型足以說明
不要使用只有一兩個字母的參數名
不要使用簡寫,拼出完整的單詞
下面列舉了一些常用參數名:
存取方法( Accessor Methods )
存取方法是指用來獲取和設置類屬性值的方法,屬性的不同類型,對應著不同的存取方法規范:
命名存取方法時不要將動詞轉化為被動形式來使用:
可以使用 can,should,will 等詞來協助表達存取方法的意思,但不要使用 do, 和 does :
為什麼 Objective-C 中不適用 get 前綴來表示屬性獲取方法?因為 get 在 Objective-C 中通常只用來表示從函數指針返回值的函數:
命名委托( Delegate )
當特定的事件發生時,對象會觸發它注冊的委托方法。委托是 Objective-C 中常用的傳遞消息的方式。委托有它固定的命名范式。
一個委托方法的第一個參數是觸發它的對象,第一個關鍵詞是觸發對象的類名,除非委托方法只有一個名為 sender 的參數:
根據委托方法觸發的時機和目的,使用should
,will
,did
等關鍵詞
集合操作類方法( Collection Methods )
有些對象管理著一系列其它對象或者元素的集合,需要使用類似 “ 增刪查改 ” 的方法來對集合進行操作,這些方法的命名范式一般為:
注意,如果返回的集合是無序的,使用 NSSet 來代替 NSArray 。如果需要將元素插入到特定的位置,使用類似於這樣的命名:
如果管理的集合元素中有指向管理對象的指針,要設置成 weak 類型以防止引用循環。
下面是 SDK 中 NSWindow 類的集合操作方法:
命名函數( Functions )
在很多場合仍然需要用到函數,比如說如果一個對象是一個單例,那麼應該使用函數來代替類方法執行相關操作。
函數的命名和方法有一些不同,主要是:
函數名稱一般帶有縮寫前綴,表示方法所在的框架。
前綴後的單詞以 “ 駝峰 ” 表示法顯示,第一個單詞首字母大寫。
函數名的第一個單詞通常是一個動詞,表示方法執行的操作:
如果函數返回其參數的某個屬性,省略動詞:
如果函數通過指針參數來返回值,需要在函數名中使用 Get :
函數的返回類型是 BOOL 時的命名:
命名屬性和實例變量( Properties&Instance Variables )
屬性和對象的存取方法相關聯,屬性的第一個字母小寫,後續單詞首字母大寫,不必添加前綴。屬性按功能命名成名詞或者動詞:
屬性也可以命名成形容詞,這時候通常會指定一個帶有 is 前綴的 get 方法來提高可讀性:
命名實例變量,在變量名前加上 _ 前綴( 有些有歷史的代碼會將 _ 放在後面 ),其它和命名屬性一樣:
一般來說,類需要對使用者隱藏數據存儲的細節,所以不要將實例方法定義成公共可訪問的接口,可以使用 @private , @protected 前綴。
按蘋果的說法,不建議在除了 init 和 dealloc 方法以外的地方直接訪問實例變量,但很多人認為直接訪問會讓代碼更加清晰可讀,只在需要計算或者執行操作的時候才使用存取方法訪問,我就是這種習慣,所以這裡不作要求。
命名常量( Constants )
如果要定義一組相關的常量,盡量使用枚舉類型( enumerations ),枚舉類型的命名規則和函數的命名規則相同:
使用匿名枚舉定義 bit map :
使用 const 定義浮點型或者單個的整數型常量,如果要定義一組相關的整數常量,應該優先使用枚舉。常量的命名規范和函數相同:
不要使用 #define 宏來定義常量,如果是整型常量,盡量使用枚舉,浮點型常量,使用 const 定義。 #define 通常用來給編譯器決定是否編譯某塊代碼,比如常用的:
注意到一般由編譯器定義的宏會在前後都有一個 __ ,比如 __MACH__ 。
命名通知( Notifications )
通知常用於在模塊間傳遞消息,所以通知要盡可能地表示出發生的事件,通知的命名范式是:
栗子:
注釋
讀沒有注釋代碼的痛苦你我都體會過,好的注釋不僅能讓人輕松讀懂你的程序,還能提升代碼的逼格。注意注釋是為了讓別人看懂,而不是僅僅你自己。
每一個文件都 必須 寫文件注釋,文件注釋通常包含
文件所在模塊
作者信息
歷史版本信息
版權信息
文件包含的內容,作用
一段良好文件注釋的栗子:
文件注釋的格式通常不作要求,能清晰易讀就可以了,但在整個工程中風格要統一。
好的代碼應該是 “ 自解釋 ” ( self-documenting )的,但仍然需要詳細的注釋來說明參數的意義、返回值、功能以及可能的副作用。
方法、函數、類、協議、類別的定義都需要注釋,推薦采用 Apple 的標准注釋風格,好處是可以在引用的地方 alt+ 點擊自動彈出注釋,非常方便。
有很多可以自動生成注釋格式的插件,推薦使用 VVDocumenter :
一些良好的注釋:
協議、委托的注釋要明確說明其被觸發的條件:
如果在注釋中要引用參數名或者方法函數名,使用 || 將參數或者方法括起來以避免歧義:
每個人都有自己的編碼風格,這裡總結了一些比較好的 Cocoa 編程風格和注意點。
不要使用 new 方法
盡管很多時候能用 new 代替 alloc init 方法,但這可能會導致調試內存時出現不可預料的問題。 Cocoa 的規范就是使用 alloc init 方法,使用 new 會讓一些讀者困惑。
Public API 要盡量簡潔
共有接口要設計的簡潔,滿足核心的功能需求就可以了。不要設計很少會被用到,但是參數極其復雜的 API 。如果要定義復雜的方法,使用類別或者類擴展。
#import和 #include
#import 是 Cocoa 中常用的引用頭文件的方式,它能自動防止重復引用文件,什麼時候使用 #import ,什麼時候使用 #include 呢?
當引用的是一個 Objective-C 或者 Objective-C++ 的頭文件時,使用 #import
當引用的是一個 C 或者 C++ 的頭文件時,使用 #include ,這時必須要保證被引用的文件提供了保護域( #define guard )。
栗子:
為什麼不全部使用 #import 呢?主要是為了保證代碼在不同平台間共享時不出現問題。
上面提到過,每一個框架都會有一個和框架同名的頭文件,它包含了框架內接口的所有引用,在使用框架的時候,應該直接引用這個根頭文件,而不是其它子模塊的頭文件,即使是你只用到了其中的一小部分,編譯器會自動完成優化的。
BOOL 的使用
BOOL 在 Objective-C 中被定義為 signed char 類型,這意味著一個 BOOL 類型的變量不僅僅可以表示 YES(1) 和 NO(0) 兩個值,所以永遠 不要 將 BOOL 類型變量直接和 YES 比較:
同樣的,也不要將其它類型的值作為 BOOL 來返回,這種情況下, BOOL 變量只會取值的最後一個字節來賦值,這樣很可能會取到 0 ( NO )。但是,一些邏輯操作符比如 &&,||,! 的返回是可以直接賦給 BOOL 的:
另外 BOOL 類型可以和 _Bool,bool 相互轉化,但是 不能 和 Boolean 轉化。
除非想要兼容一些古董級的機器和操作系統,我們沒有理由放棄使用 ARC 。在最新版的 Xcode(6.2) 中, ARC 是自動打開的,所以直接使用就好了。
在 init 和 dealloc 中不要用存取方法訪問實例變量
當 initdealloc 方法被執行時,類的運行時環境不是處於正常狀態的,使用存取方法訪問變量可能會導致不可預料的結果,因此應當在這兩個方法內直接訪問實例變量。
按照定義的順序釋放資源
在類或者 Controller 的生命周期結束時,往往需要做一些掃尾工作,比如釋放資源,停止線程等,這些掃尾工作的釋放順序應當與它們的初始化或者定義的順序保持一致。這樣做是為了方便調試時尋找錯誤,也能防止遺漏。
保證 NSString 在賦值時被復制
NSString 非常常用,在它被傳遞或者賦值時應當保證是以復制( copy )的方式進行的,這樣可以防止在不知情的情況下 String 的值被其它對象修改。
使用 NSNumber 的語法糖
使用帶有 @ 符號的語法糖來生成 NSNumber 對象能使代碼更簡潔:
nil 檢查
因為在 Objective-C 中向 nil 對象發送命令是不會拋出異常或者導致崩潰的,只是完全的 “ 什麼都不干 ” ,所以,只在程序中使用 nil 來做邏輯上的檢查。
另外,不要使用諸如 nil == Object 或者 Object == nil 的形式來判斷。
屬性的線程安全
定義一個屬性時,編譯器會自動生成線程安全的存取方法( Atomic ),但這樣會大大降低性能,特別是對於那些需要頻繁存取的屬性來說,是極大的浪費。所以如果定義的屬性不需要線程保護,記得手動添加屬性關鍵字 nonatomic 來取消編譯器的優化。
不要用點分語法來調用方法,只用來訪問屬性。這樣是為了防止代碼可讀性問題。
Delegate 要使用弱引用
一個類的 Delegate 對象通常還引用著類本身,這樣很容易造成引用循環的問題,所以類的 Delegate 屬性要設置為弱引用。