OC 編碼規(guī)范

這篇文檔包含了一系列在 iOS 代碼中推薦的指導(dǎo)方針、編碼慣例和編寫代碼的最佳實(shí)踐,主要是為了提高代碼的可讀性。我們應(yīng)該知道,永遠(yuǎn)是讀代碼的時(shí)間比寫代碼的時(shí)間多,我們超過80%的時(shí)間都是在維護(hù)代碼。

有一點(diǎn)是需要注意的,編寫本篇規(guī)范不是為了說服所有人下面所寫的每一條都是編碼最好的方式,而是為了推薦大家一系列的準(zhǔn)則來保證我們的代碼風(fēng)格更加一致、可理解并且遠(yuǎn)離晦澀的取向。

一、編碼的格式及慣例

空格、空行

  • 縮排使用4個(gè)空格(一般 tab 就是4個(gè)空格)
  • 確保 Xcode 配置中Automatically trim trailing whitespaceIncluding whitespace-only lines已經(jīng)勾選(譯者注:我覺得包括空行這項(xiàng)值得商榷)

方法聲明

我們應(yīng)該延續(xù) Xcode 中使用的方法聲明模板:-后面使用一個(gè)空格,除了參數(shù)之間不應(yīng)該有其他的地方存在空格。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...
}

流程控制語句(條件判斷/循環(huán)等等)

括號(hào)應(yīng)該同條件在同一行:

if (foobar) {
    ...
} else {
    ...
}
for (foo; foo; foo) {
    ...
}

單行流程控制語句依然應(yīng)該使用括號(hào)(不要省略)。

命名

這里需要大家看一下 Apple's Coding Guidelines 特別是方法命名指南這一塊,舉例:
使用 - (NSSize)cellSize 而不是- (NSSize)getCellSize

指針聲明

對(duì)于指針來說,應(yīng)該將星號(hào)放置于變量名的前面,例如:NSString *foo。

使用字面量

盡可能使用 Objc 字面量來定義字典、數(shù)組和 number,例如:使用@42而不是[NSNumber numberWithInt:42]或者:

NSDictionary *dictionary = @{
    @”k1” : @”v1”,
    @”k2” : @”v2”,
};

而不是
[NSDictionary dictionaryWithObjectsAndKeys:@”v1”, @”k1”, @”v2”, @”k2”, nil]

只在用@property 聲明的屬性上使用點(diǎn)語法

只在聲明了的屬性或者結(jié)構(gòu)上使用點(diǎn)語法,不要這樣用:foobar.release或者foobar.count

原因是因?yàn)椋狐c(diǎn)語法是圍繞 getter/setter 調(diào)用的語法糖,只要用在屬性上才是符合慣例的。

ivar 實(shí)例命名使用下劃線 _ 前綴

ivar 實(shí)例命名應(yīng)該使用下劃線前綴,例如:NSString *_foobar

默認(rèn) property 生成的 ivar 就是以下劃線作為前綴,我們應(yīng)該保持風(fēng)格統(tǒng)一。

聲明屬性時(shí)限定語要明確

對(duì)于屬性的限定語是 nonatomic/atomic、strong/weak 要明確。

不要使用共有變量(public ivar)

外部調(diào)用應(yīng)該無法直接修改變量,如有需要,提供 getter/setter 方法給外部調(diào)用;

私有變量放到@implementation 代碼塊中

例如:

@implementation
{
    NSString *_foobar;
}

對(duì)于#import 引用應(yīng)該進(jìn)行排序

對(duì)于引用的頭文件按照字符次序進(jìn)行排序,并且本地文件的引用放在前面(對(duì)于 .m 文件,對(duì)于自己的頭文件的引用放在第一位),全局文件的引用放在后面,例如對(duì)于 DBFoo.m:

#import "DBFoo.h"
#import "DBFoo+Protected.h"

#import "DBAnotherFile.h"
#import "DBSomeOtherFile.h"
#import <CoolFramework/Header.h>
#import <FoobarFramework/Foo.h>

使用#pragma mark 來根據(jù)功能標(biāo)記一組代碼

使用#pragma mark來標(biāo)記一組方法的邏輯含義,例如實(shí)現(xiàn)的一個(gè)特定協(xié)議的方法,或者對(duì)象的 setter/getter 方法。在一個(gè)區(qū)域的子區(qū)域中,可以使用#pragma mark -來創(chuàng)建 Xcode 的導(dǎo)航分隔標(biāo)記。例如,在一個(gè) tableViewController 中我們可以這樣使用:

#pragma mark - Lifecycle // for init and dealloc methods
#pragma mark - Appearance
#pragma mark view initializations
#pragma mark view utilities
#pragma mark - UIViewController overrides
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

為了使代碼易讀、易查找,方法應(yīng)該按照功能進(jìn)行劃分,而不是根據(jù)方法的可訪問范圍。例如一個(gè)私有的類方法可以放在兩個(gè)共有的實(shí)例方法之間。

審視 switch 代碼,避免包羅萬象的現(xiàn)象(catch-alls)

對(duì)于復(fù)雜的 switch-case 代碼塊,將每個(gè) case 代碼使用括號(hào)標(biāo)明代碼塊,例如:

switch (foo) {
    case 0: {
        ...
    }
    break;
    ...
}

如果可以的話,盡量避免使用 default 代碼塊來代表“其他情況”,應(yīng)該使用default 代碼塊代表異常的情況。

為私有方法和分類方法添加前綴

例如:- (void)db_flushQueue

原因:這樣做可以很容易的識(shí)別出這個(gè)方法是否是一個(gè)私有方法,并也可以盡量減少子類無意中重寫父類私有方法的可能性。

如果私有方法不依賴實(shí)例狀態(tài)的話,將其改寫為類方法

如果一個(gè)私有方法沒有使用成員變量,將其改寫為類方法。

TODO/FIXME

使用姓名、時(shí)間來標(biāo)記 TODO 和 FIXME:

TODO:(rich) 2013-09-13 finish the style guide!

使用TODO:(<name>) <date>的格式來保證風(fēng)格一致、便于查找。
使用 FIXME 來標(biāo)記在 push 之前急需修改的內(nèi)容。

對(duì)于常量使用 static const

使用 static const 來代替#define 語句:
static const CGFloat kFooBar = 123.0;

原因:這樣編寫可以代表常量的作用域,是類型安全的,并且這樣編碼常量是通過變量符號(hào)裝載進(jìn)內(nèi)存,在調(diào)試模式下便于訪問。

使用 NSUIntegers/NSIntegers 代替 int(CGFloat 代替 float)

除非是特定的 API 庫指定類型為 int/float,我們應(yīng)該使用 NSUIntegers/NSIntegers/CGFloat。

原因:第一,大多數(shù) cocoa 庫偏向使用 NSUIntegers/NSIntegers/CGFloat,我們?cè)谑褂?cocoa 的時(shí)候使用NSUIntegers/NSIntegers/CGFloat 更加便捷;第二,int 類型占用的位數(shù)會(huì)根據(jù)系統(tǒng)架構(gòu)不同而變化,使用 NSUIntegers/NSIntegers/CGFloat 更加可靠。

枚舉聲明

就像2012年 WWDC 中介紹的那樣,我們應(yīng)該使用NS_ENUM來聲明枚舉:

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

原因:NS_ENUM 有更好的類型校驗(yàn)和更好的代碼補(bǔ)全。

位移常量

NS_ENUM 相似,在 iOS6 以后使用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
};

類名前面添加前綴

例如:自定義的視圖控制器命名為:DBFoobarViewController。

原因:OC 不支持命名空間。

對(duì)比使用下標(biāo),偏向使用 enumerator 進(jìn)行遍歷

使用enumerateObjectsUsingBlock或者for-in 遍歷代替for-index 遍歷。

二、最佳實(shí)踐,遠(yuǎn)離一些坑

使用 NSCAssert 進(jìn)行斷言

我們應(yīng)該使用NSCAssert而不是assert 或者NSAssert 來進(jìn)行斷言。

原因:NSAssert是一個(gè)宏,實(shí)際上,它內(nèi)部對(duì)self 有一個(gè)引用,也就是說,如果在 block 中使用的斷言的話,可能會(huì)在不注意的情況下對(duì)self 產(chǎn)生一個(gè)引用,我們可以在無所謂引不引用self 的地方使用NSAssert。但是因?yàn)?code>NSAssert 和NSCAssert 作用大致相同(都包含文件名、方法名和行數(shù),但是NSCAssert不打印self的實(shí)際類型),我們可以直接簡(jiǎn)單的使用NSCAssert。

存儲(chǔ) block 的時(shí)候使用 copy

當(dāng)在一個(gè)方法的外部你希望引用一個(gè) block 變量的時(shí)候(例如通過把它存儲(chǔ)為一個(gè) ivar),應(yīng)該對(duì)其進(jìn)行 copy。

原因:block 開始被創(chuàng)建在棧內(nèi)存上,如果在超出作用域?qū)?block 進(jìn)行引用,應(yīng)該將其 copy 到堆上。

對(duì) NSString 使用 copy

原因:對(duì)于外部訪問者傳入的 NSString 使用 copy 是為了防止傳入 NSMutableString。

永遠(yuǎn)不要將整型數(shù)據(jù)強(qiáng)制轉(zhuǎn)換為 BOOL 類型

例如常見的將一個(gè)數(shù)組的長(zhǎng)度賦給一個(gè) BOOL 類型的變量。

原因:即使整型值大于0,當(dāng)其二進(jìn)制后8位全都是0的時(shí)候,BOOL 值依然為0。

對(duì)于私有變量不要使用@property 聲明屬性

在.m文件中不要使用@property,使用 ivar 成員變量就好。
有一種例外情況:少有的需要使用atomic 屬性的時(shí)候,不需遵循以上原則。

在 dealloc 方法中,清除指向?qū)ο蟮?delegate 或者 observer

如果一個(gè)對(duì)象被設(shè)置為另一些對(duì)象的代理或者觀察者(包括通知對(duì)象),在dealloc 方法中應(yīng)該將其設(shè)置為nil
注意:即使在ARC 環(huán)境下,delegate 屬性被設(shè)置為 weak,delegate 對(duì)象的指針被設(shè)置為 nil 依然很重要,原因是因?yàn)槔锩嬗泻芏嗟那闆r依然是按照 assign 的規(guī)則編譯,如果出現(xiàn)這種情況,再訪問的時(shí)候就會(huì)出現(xiàn)崩潰。

對(duì)局部變量進(jìn)行初始化

ivar 會(huì)被自動(dòng)的初始化為 nil 或者0,所以你不需要顯式的將其聲明為 nil,但是對(duì)于局部變量則不然,你需要手動(dòng)的將其初始化。

視圖控制器中,在viewDidLoad之前不要碰 view

調(diào)用self.view會(huì)導(dǎo)致 view 的加載,通常在viewDidLoad之前并不需要這么做。

相比 target-selector 代理模式更偏向使用 block 來完成回調(diào)

對(duì)于簡(jiǎn)單的回調(diào),我們應(yīng)該傾向使用 block 來代替 target-selector代理模式來完成回調(diào)。

原因:這樣做能夠降低代碼的分離、碎片性,比如想想對(duì)于一個(gè)簡(jiǎn)單的回調(diào),相比定義代理協(xié)議、遵循協(xié)議、實(shí)現(xiàn)協(xié)議,如下使用 block 會(huì)顯得更加清晰:

[op startWIthCompletionHandler:^() {
    // ... stuff to be done after the op finishes ...
}];

對(duì)于支持多線程操作的類、方法、變量應(yīng)該添加更加詳細(xì)的注釋文檔

通常情況下我們會(huì)假定所有的方法僅僅只會(huì)在主線程上執(zhí)行,如果一個(gè)方法是預(yù)期在后臺(tái)線程上執(zhí)行的話(例如:某些蘋果的通知,像ALAssetsLibraryChangedNotification),請(qǐng)確保添加相對(duì)應(yīng)的注釋并且如果可能的話添加斷言(例如:DBAssertMainThread)。
同樣的,我們通常會(huì)假定所有的實(shí)例變量只會(huì)從主線程訪問,如果某個(gè)變量需要在線程間共享使用(或者僅僅只在一個(gè)特定的線程、隊(duì)列中使用),請(qǐng)對(duì)變量確保添加了相應(yīng)的注釋。
對(duì)于類來說,如果是被設(shè)計(jì)成含有線程安全的接口,需要添加對(duì)應(yīng)的注釋來說明其工作的情況。

如無必要,避免使用原子屬性

默認(rèn)情況下,所有的屬性都是原子屬性的,需要你顯式的將其標(biāo)明為非原子屬性。

原因:設(shè)置原子屬性會(huì)對(duì) setter/getter 方法加鎖,這樣會(huì)嚴(yán)重影響性能。

使用 GCD 來代替performSelectorInBackground

通常來說,永遠(yuǎn)也不要使用performSelectorInBackground

原因:使用 GCD 會(huì)讓后臺(tái)線程執(zhí)行的代碼與執(zhí)行代碼放到一起更加有利于理解和閱讀。

避免頭文件中不必要的引用

如果可以的話,使用前向聲明來代替頭文件的引用,例如:@class DBFoo。

原因:減少編譯時(shí)間和不必要的依賴。

對(duì)指定構(gòu)造方法進(jìn)行文檔注釋

對(duì)于指定的構(gòu)造方法要標(biāo)注清楚,只有這樣,子類對(duì)構(gòu)造方法進(jìn)行重寫的時(shí)候才知道重寫哪些構(gòu)造方法能夠確保子類的構(gòu)造方法被調(diào)用。

子類應(yīng)該對(duì)父類的指定構(gòu)造方法進(jìn)行重寫

如果方法調(diào)用方不應(yīng)該再調(diào)用這個(gè)構(gòu)造方法時(shí),應(yīng)該使用拋出斷言進(jìn)行警告。

通知中心的觀察者方法應(yīng)該帶有NSNotification參數(shù)

[[NSNotificationCenter defaultCenter] addObserver:self
                                        selector:@selector(db_updateStuffWithNotification:)
                                            name:@"SomeNotificationName"
                                          object:theSender];

原因:即使并不需要參數(shù)的情況下(例如:觀察者的方法不需要參數(shù)),這樣做可以使代碼閱讀者更加明顯的發(fā)現(xiàn)這個(gè)方法是被通知中心所調(diào)用。

Block 及循環(huán)引用

Block 中訪問self或者使用變量間接引用了self的情況下,Block 會(huì)隱式的創(chuàng)建self的強(qiáng)引用。
換句話說,在使用 Block 的時(shí)候可能會(huì)引起循環(huán)引用。一個(gè)通常的解決方案是:舉個(gè)例子來說,比如你使用了一個(gè)BlockAlertView在回調(diào)用的 block 里面引用了self,并且self又強(qiáng)引用了這個(gè)BlockAlertView,這樣就會(huì)引起一個(gè)self -> alert view -> block -> self的引用循環(huán),為了避免這種情況的發(fā)生,我們可以使用一種通常被稱為strong-weak dance的方法:

__weak DBFoo *weakSelf = self;
[_someRandomViewIOwn setObserverBlock:^{
    DBFoo *strongSelf = weakSelf;
    // NOTE: strongSelf may point to nil if weakSelf is already nil'ed out prior to the block being called!
    [strongSelf doSomeStuff];
}];

原因:通過使用weakSelf,block 不再強(qiáng)持有self,但是還是有可能出現(xiàn)在 block 調(diào)用之前weakSelf 變成nil 的情況,更糟的是,如果 block 是在多個(gè)線程中被調(diào)用的,weakSelf 還有可能是在 block 調(diào)用過程中被置為 nil。
為了避免在 block 執(zhí)行過程中出現(xiàn)self突然消失的情況,我們?nèi)藶榈慕oweakSelf添加一個(gè)強(qiáng)引用strongSelf,雖然strongSelf仍然有可能為 nil,但是起碼在 block 執(zhí)行過程中會(huì)保持始終如一,不必再擔(dān)心會(huì)隨意的置為 nil。

但是,你也不應(yīng)該濫用這種處理方式,在絕大多是情況下,你想使用延遲執(zhí)行的 block,(例如:動(dòng)畫、dispatch_after),不應(yīng)該使用這種方式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 好的代碼有一些特性:簡(jiǎn)明,自我解釋,優(yōu)秀的組織,良好的文檔,良好的命名,優(yōu)秀的設(shè)計(jì)以及可以被久經(jīng)考驗(yàn)。 本文參考若...
    BigTing閱讀 701評(píng)論 1 2
  • 建議 為了項(xiàng)目的“整潔性”不建議同時(shí)使用多語言開發(fā); 在導(dǎo)入第三方框架之前,充分考慮導(dǎo)入此框架的必要性和風(fēng)險(xiǎn),遵循...
    青蘋果園閱讀 942評(píng)論 0 0
  • 目錄1.格式和換行 2.命名 3.oc下的cocoa編碼規(guī)范 4.注釋要求 5.其他 6.參考文檔 附:ARC下編...
    wzf_taker閱讀 679評(píng)論 0 0
  • 禪與 Objective-C 編程藝術(shù) (Zen and the Art of the Objective-C C...
    GrayLand閱讀 1,810評(píng)論 1 10
  • 一場(chǎng)感冒大約一周痊愈,三場(chǎng)感冒的時(shí)間足夠強(qiáng)大一個(gè)人。
    一本兔子書閱讀 415評(píng)論 0 0

友情鏈接更多精彩內(nèi)容