Objective-C 編程規范

命名規范

1.【強制】 代碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束。

反例: _name / __name / $Object / name_ / name$ / Object$

2.【強制】 代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。

說明:正確的英文拼寫和語法可以讓閱讀者易于理解,避免歧義。注意,即使純拼音命名方式 也要避免采用。

反例:DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量 = 3

正例:alibaba / taobao / youku / hangzhou 等國際通用的名稱,可視同英文。

3.【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但領域模型相關的命名除外,比如 UserDAO

正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion

反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

4.【強制】方法名、參數名、成員變量、局部變量都統一使用 lowerCamelCase 風格,必須遵從 駝峰形式。

正例:localValue / getHttpMessage / inputUserId

5.【強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。

正例:MAX_STOCK_COUNT

反例:MAX_COUNT

6.【強制】異常類命名使用 Exception 結尾;測試類命名以它要測試的類的名稱開始,以 Test 結尾。

7.【強制】杜絕完全不規范的縮寫,避免望文不知義。

8.【強制】文件名和類名同名。

9.【推薦】如果使用到了設計模式,建議在類名中體現出具體模式。

說明:將設計模式體現在名字中,有利于閱讀者快速理解架構設計思想。

10.【推薦】類名應加上三個大寫字母作為前綴(兩個字母的為 Apple 的類保留)。

11.【強制】特殊類的命名,如果是視圖控制器的子類應添加后綴“ViewController”或者“Controller”,如果是視圖的子類應添加后綴“View”,如果是按鈕的子類應添加后綴“Button”。

12.【強制】分類 Category 命名,與類命名相同,此外需添加要擴展的類名和“+”

13.【強制】協議(委托)命名,與類命名相同,此外需添加“Delegate”后綴

14.【推薦】方法返回值為Boolean 類型,應加 is 前綴。如果某方法返回非屬性的 Boolean 值,那么應該根據其功能,選用 has 或 is 當前綴。

- (BOOL)isEqualToString:(NSString *)aString; 
- (BOOL)hasPrefix:(NSString *)aString;

15.【推薦】方法名不要使用 new 作為前綴

16.【推薦】不要使用 and 來連接用屬性作參數的關鍵字

17.【推薦】方法連接多個參數命名時,適當使用合適的介詞。比如 in for at by of with

//清晰
insertObject:atIndex:

//不清晰,insert的對象類型和at的位置屬性沒有說明
insert:at:

//清晰
removeObjectAtIndex:

//不清晰,remove的對象類型沒有說明,參數的作用沒有說明
remove:

18.【推薦】定義成員變量,添加下劃線前綴。但不推薦使用成員變量,直接使用屬性。

19.【強制】資源圖片命名,圖片應該與類文件一樣,按模塊分組放置,縮略前綴可用如下例子

icon btn bg line logo pic img

常量定義

1.【強制】不允許出現任何魔法值(即未經定義的常量)直接出現在代碼中。

2.【推薦】不要使用一個常量類維護所有常量,應該按常量功能進行歸類,分開維護。

如:緩存相關的常量放在類:CacheConsts下;系統配置相關的常量放在類:ConfigConsts下。

說明:大而全的常量類,非得使用查找功能才能定位到修改的常量,不利于理解和維護。

3.【強制】常量使用小寫k開頭的駝峰法。

static const NSTimeInterval kAnimationDuration = 0.3;

4.【強制】若常量作用域超出編譯單元,在類外可見時,

.h
extern NSString *const EOCStringConstant;

.m
NSString *const EOCStringConstant = @"aaa";

5.【強制】全局常量(通知或者關鍵字等)盡量用const來定義,而不是宏。因為如果使用宏定義, 宏可能被重定義,引用不同的文件可能會導致宏的不同。

格式規范

1.【強制】大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行;如果 是非空代碼塊則:

    1. 左大括號前不換行。
    1. 左大括號后換行。
    1. 右大括號前換行。
    1. 右大括號后還有else等代碼則不換行;表示終止右大括號后必須換行。

2.【強制】 左括號和后一個字符之間不出現空格;同樣,右括號和前一個字符之間也不出現空格。

3.【強制】if/for/while/switch/do 等保留字與左右括號之間都必須加空格。

4.【強制】任何運算符左右必須加一個空格。

說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號、三目運算符等。

5.【強制】縮進采用 4 個空格,禁止使用 tab 字符。

- (void)method {
    // 縮進 4 個空格
    NSString *say = @"hello";
    // 運算符的左右必須有一個空格
    NSInteger flag = 0;
    // 關鍵詞 if 與括號之間必須有一個空格,括號內的 f 與左括號,0 與右括號不需要空格
    if (flag == 0) {
        NSLog(@"%@", say);
    }
    
    // 左大括號前不加空格且不換行,左大括號后換行
    if (flag == 1) {
        NSLog(@"world");
        // 右大括號前換行,右大括號后有 else,不用換行
    } else {
        NSLog(@"ok");
        // 在右大括號后直接結束,則必須換行
    }
}

6.【強制】單行字符數限制不超過 120 個,超出需要換行,按照:來對齊分行顯示

-(id)initWithModel:(IPCModle)model
       ConnectType:(IPCConnectType)connectType
        Resolution:(IPCResolution)resolution
          AuthName:(NSString *)authName
          Password:(NSString *)password
               MAC:(NSString *)mac
              AzIp:(NSString *)az_ip
             AzDns:(NSString *)az_dns
             Token:(NSString *)token
             Email:(NSString *)email
          Delegate:(id<IPCConnectHandlerDelegate>)delegate;

在分行時,如果第一段名稱過短,后續名稱可以以Tab的長度(4個空格)為單位進行縮進:

- (void)short:(GTMFoo *)theFoo
        longKeyword:(NSRect)theRect
  evenLongerKeyword:(float)theInterval
              error:(NSError **)theError {
    ...
}

函數的調用同理。

//寫在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];

//分行寫,按照':'對齊
[myObject doFooWith:arg1
               name:arg2
              error:arg3];

//第一段名稱過短的話后續可以進行縮進
[myObj short:arg1
          longKeyword:arg2
    evenLongerKeyword:arg3
                error:arg4];

7.【強制】多個參數逗號后邊必須加空格。

正例:

NSLog(@"%@-%@-%@-%@", a, b, c, d);

8.【推薦】沒有必要增加若干空格來使某一行的字符與上一行的相應字符對齊。

說明:在變量比較多的 情況下,是一種累贅的事情。

@interface HOECoupon : NSObject

@property (nonatomic, assign) NSInteger couponId;
@property (nonatomic, copy) NSString *startTime;
@property (nonatomic, copy) NSString *endTime;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subTitle;
@property (nonatomic, assign) NSInteger state;
@property (nonatomic, assign) NSInteger canUse;
@property (nonatomic, assign) CGFloat price;
@property (nonatomic, copy) NSString *stateDesc;
@property (nonatomic, copy) NSString *reason;

@end

9.【推薦】方法體內的執行語句組、變量的定義語句組、不同的業務邏輯之間或者不同的語義之間插入一個空行。相同業務邏輯和語義之間不需要插入空行。

說明:沒有必要插入多行空格進行隔開。

10.【推薦】.m實現類中推薦的代碼結構如下

#pragma mark - lifecycle
- (void)dealloc
- (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
...
#pragma mark - UITableViewDataSource & UITableViewDelegate
...
#pragma mark - Event handlers
- (void)didTapLikeButton:
...
#pragma mark - public methods
...
#pragma mark - private methods
- (void)setupTableView
- (void)setupNotification
...
#pragma mark - getters & setters
...

11.【推薦】頭文件引用的順序

//  controllers
#import "OrcaAddFriendFromContactsViewController.h"
...
// views
...
// consts
...
// utils
...
// others

12.【推薦】方法的格式,在-和(void)之間應該有一個空格

- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
    ...
}

面向對象規范

1.【強制】避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析成本,直接用類名來訪問即可。

2.【強制】不能使用過時的類或方法。

3.【強制】推薦使用 NS_ENUM ,而不是 enum

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

4.【推薦】盡量使用字面量來初始化

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

5.【推薦】建議定義屬性的時候把所有的參數寫全, 尤其是如果想定義成只讀的(防止外面修改)那一定要加上readonly, 這也是代碼安全性的一個習慣。

@property (nonatomic, readwrite, copy) NSString *name;

6.【強制】方法參數過多,需要進行重構

正例:

- (void)registerUser(User *user) {
     // to do...
}

反例:

- (void)registerUserName:(NSString *)userName
                password:(NSString *)password 
                   email:(NSString *)email {
     // to do...
}

7.【強制】不推薦使用成員變量,直接使用屬性。

8.【推薦】setter 方法中,參數名稱與類成員變量名稱一致,self.成員名 = 參數名。在getter/setter 方法中,盡量不要增加業務邏輯,增加排查問題的難度。

9.【推薦】對于簡單對象,推薦在getter方法中延遲加載屬性,如果創建復雜對象,那么對象的創建最好放到類的初始化時,而不是延遲加載。

10.【推薦】協議,在書寫協議的時候注意用<>括起來的協議和類型名之間是沒有空格的

@interface MyProtocoledClass : NSObject<NSWindowDelegate> {
}

- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end

11.【推薦】閉包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那行代碼的第一個非空字符對齊
[[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) {
        // ...
    }];

12.【推薦】不要用點分語法來調用方法,只用來訪問屬性。這樣是為了防止代碼可讀性問題。

//正確,使用點分語法訪問屬性
NSString *oldName = myObject.name;
myObject.name = @"Alice";

//錯誤,不要用點分語法調用方法
NSArray *array = [NSArray arrayWithObject:@"hello"];
NSUInteger numberOfItems = array.count;

13.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變量只會取值的最后一個字節來賦值,這樣很可能會取到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];
}

14.不要使用new方法

盡管很多時候能用new代替alloc init方法,但這可能會導致調試內存時出現不可預料的問題。Cocoa的規范就是使用alloc init方法,使用new會讓一些讀者困惑。

15.Public API要盡量簡潔

共有接口要設計的簡潔,滿足核心的功能需求就可以了。不要設計很少會被用到,但是參數極其復雜的API。如果要定義復雜的方法,使用類別或者類擴展。

集合處理

1.【強制】對于可變集合類型作為屬性時使用strong,不可變集合類型使用copy

2.【強制】盡量不要暴露mutable類型的對象在public interface, 建議在.h定義一個Inmutable類型的屬性, 然后在.m的get函數里面返回一個內部定義的mutable變量。保證類的不可變性

3.使用NSNumber的語法糖

使用帶有@符號的語法糖來生成NSNumber對象能使代碼更簡潔:

NSNumber *fortyTwo = @42;
NSNumber *piOverTwo = @(M_PI / 2);
enum {
  kMyEnum = 2;
};
NSNumber *myEnum = @(kMyEnum);

4.保證NSString在賦值時被復制

NSString非常常用,在它被傳遞或者賦值時應當保證是以復制(copy)的方式進行的,這樣可以防止在不知情的情況下String的值被其它對象修改。

- (void)setFoo:(NSString *)aFoo {
  _foo = [aFoo copy];
}

5.按照定義的順序釋放資源

在類或者Controller的生命周期結束時,往往需要做一些掃尾工作,比如釋放資源,停止線程等,這些掃尾工作的釋放順序應當與它們的初始化或者定義的順序保持一致。這樣做是為了方便調試時尋找錯誤,也能防止遺漏。

并發處理

1.【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。

2.定義一個屬性時,編譯器會自動生成線程安全的存取方法(Atomic),但這樣會大大降低性能,特別是對于那些需要頻繁存取的屬性來說,是極大的浪費。所以如果定義的屬性不需要線程保護,記得手動添加屬性關鍵字nonatomic來取消編譯器的優化。

控制語句

1.【強制】在一個 switch 塊內,每個 case 要么通過 break/return 等來終止,要么注釋說明程 序將繼續執行到哪一個 case 為止;在一個 switch 塊內,都必須包含一個 default 語句并且 放在最后,即使它什么代碼也沒有。

2.【強制】在 if/else/for/while/do 語句中必須使用大括號,即使只有一行代碼,避免使用 下面的形式:if (condition) statements;

3.【推薦】推薦盡量少用 else, if-else 的方式可以改寫成:

if (condition) { 
    ...
    return obj; 
}
// 接著寫 else 的業務邏輯代碼;

4.【強制】請勿超過 3 層,超過請使用狀態設計模式。

正例:邏輯上超過 3 層的 if-else 代碼可以使用衛語句,或者狀態模式來實現。

5.【強制】三目運算符的使用

正例:

result = object ? : [self createObject];

反例:

result = object ? object : [self createObject];

6.【強制】對于復雜的條件判斷,要提取一個變量出來

正例:

([self canDeleteJob:job]) { ... }     
    
- (BOOL)canDeleteJob:(Job *)job {
    BOOL invalidJobState = job.JobState == JobState.New
                          || job.JobState == JobState.Submitted
                          || job.JobState == JobState.Expired;
    BOOL invalidJob = job.JobTitle && job.JobTitle.length;
     
    return invalidJobState || invalidJob;
}

反例:

if (job.JobState == JobState.New
    || job.JobState == JobState.Submitted
    || job.JobState == JobState.Expired
    || (job.JobTitle && job.JobTitle.length))
{
    //....
}

7.【強制】對于嵌套判斷,一旦發現某個條件不符合,立即返回,條理更清晰

正例:

if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
 
return YES;

反例:

Not preferred:
BOOL isValid = NO;
if (user.UserName)
{
 
    if (user.Password)
    {
     
        if (user.Email) isValid = YES;
    }
     
}
return isValid;

注釋規范

1.【強制】類、類屬性、類方法的注釋必須使用文檔注釋,不得使用 //xxx 方式。

2.【強制】所有的類都必須添加創建者信息。

3.【強制】方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋
使用/* */注釋,注意與代碼對齊。

4.【強制】所有的枚舉類型字段必須要有注釋,說明每個數據項的用途。

5.【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數、返回值、異常、核心邏輯 等的修改。

6.【參考】注釋掉的代碼盡量要配合說明,而不是簡單的注釋掉。

說明:代碼被注釋掉有兩種可能性:1)后續會恢復此段代碼邏輯。2)永久不用。前者如果沒 有備注信息,難以知曉注釋動機。后者建議直接刪掉(代碼倉庫保存了歷史代碼)。

7.【參考】好的命名、代碼結構是自解釋的,注釋力求精簡準確、表達到位。避免出現注釋的一個極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當大的負擔。

8.【參考】特殊注釋標記,請注明標記人與標記時間。注意及時處理這些標記,通過標記掃描,經常清理此類標記。線上故障有時候就是來源于這些標記處的代碼。

  1. 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間]) 表示需要實現,但目前還未實現的功能。

  2. 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間])
    在注釋中用 FIXME 標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。

其他

1.【強制】避免 block 的循環引用

__weak typeof(self) weakSelf = self;
myObj.myBlock =  ^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
      [strongSelf doSomething]; // strongSelf != nil
      // preemption, strongSelf still not nil
      [strongSelf doSomethingElse]; // strongSelf != nil
    }
    else {
        // Probably nothing...
        return;
         
    }
};

2.Delegate要使用弱引用

一個類的Delegate對象通常還引用著類本身,這樣很容易造成引用循環的問題,所以類的Delegate屬性要設置為弱引用。

/** delegate */
@property (nonatomic, weak) id <IPCConnectHandlerDelegate> delegate;

3.nil檢查
因為在Objective-C中向nil對象發送命令是不會拋出異常或者導致崩潰的,只是完全的“什么都不干”,所以,只在程序中使用nil來做邏輯上的檢查。

另外,不要使用諸如nil == Object或者Object == nil的形式來判斷。

//正確,直接判斷
if (!objc) {
    ... 
}

//錯誤,不要使用nil == Object的形式
if (nil == objc) {
    ... 
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 引言 一直以來都是在談如何開發, 如開發的小技巧小經驗 今天為什么突然說起編程規范來了呢? 因為在我看來, 編程規...
    諾之林閱讀 564評論 1 5
  • 方法聲明和定義 -或者+和返回類型之間須使用一個空格,括號要同行并有一個空格 方法應該像這樣: 如果函數名字太長,...
    Dayon閱讀 244評論 0 1
  • 本文記錄一下Objective-C編程規范以及一些建議,可能后續會有些修改和補充,至于初衷是最近接手的項目中代碼"...
    SuperMario_Nil閱讀 1,836評論 2 13
  • 傳送門 解讀阿里Java開發手冊(v1.1.1) - 異常日志 前言 阿里Java開發手冊談不上圣經,但確實是大量...
    kelgon閱讀 4,385評論 4 50
  • 佛啊佛佛啊佛,世人拜禮誠求佛,佛途迢迢徑難尤,佛在心中不坐佛,佛在哪里成了佛,又是誰來引佛成。佛啊佛佛啊佛,南無阿...
    靛羽風漣閱讀 1,427評論 0 1