Objective-C 編碼建議

jopen 9年前發布 | 19K 次閱讀 Objective-C開發 Objective-C

原文 http://www.cocoachina.com/ios/20151118/14242.html

Objective-C 編碼建議

本文是投稿文章,作者: IOS_Tips(微信公眾號)

“神在細節之中”


Objective-C 是 C 語言的擴展,增加了動態類型和面對對象的特性。它被設計成具有易讀易用的,支持復雜的面向對象設計的編程語言。它是 Mac OS X 以及 iPhone 的主要開發語言。

Cocoa 是 Mac OS X 上主要的應用程序框架之一。它由一組 Objective-C 類組成,為快速開發出功能齊全的 Mac OS X 應用程序提供支持。

而在日常的編程中,我們除了要寫代碼,還需要去閱讀別人的代碼,熟悉過往的業務邏輯。不知,你可曾發過牢騷:這代碼怎么能這么寫呢?有些時候我們的代碼,也會被別人去讀,不知你可曾想過,當別人讀到你的代碼的時候會作何評價。誠然,“讓代碼能夠工作”是做為開發者的頭等大事。但是,代碼的可維護性卻是更加影響深遠的一件事情。你的代碼既有可能在下一個版本中被修改,也極有可能被交給另外的同事去修改。畢竟我們寫代碼,不止是在和機器溝通,而且也是在和人溝通——和其他的程序員溝通。大家都知道“學好普通話,走遍天下都不怕”,同樣的道理:寫出一手漂亮的代碼,你和誰溝通都沒問題。

即使你的原始代碼修改之后,其代碼風格和可讀性仍會影響到可維護性和可擴展性。即使代碼不復存在,你的風格和律條仍存活下來。

下面我們將圍繞一些基本的準則展開討論,目的是讓我們寫出一手漂亮的代碼,更好的用代碼與其他同事溝通,也為了提高我們代碼的可維護性和可修改性,也是為了讓我們自己工作的地方有一個愉悅的代碼環境。

(PS:當你真的按照這些看似偏執的規則去做的時候,你就真的能夠發現“偉大來自細節”,而且會受益匪淺。保劍鋒自磨礪出,梅花香自苦寒來。)

總則

1.Don’t repeat your self.

2.代碼自注釋,依靠代碼本身來表達你的設計意圖,不要依賴注釋。

3.單一指責,無論是類、函數、模塊、包盡可能令其指責純凈且單一。

4.死程序不說謊,不要因為防止Crash寫奇葩的代碼。程序Crash了,反而更容易查找錯誤。

5.借用美國童子軍軍規:讓營地比你來時更干凈。

格式

1.任意函數長度不得超過50行。

2.任意行代碼不得超過80字符。可以在設置中設置超過80個字符的提醒。

Objective-C 編碼建議

3.在定義函數的行前留白一行

4.功能相近的代碼要放在一起。

5.使用#pragma來切分不同功能區域的代碼。

6.二元運算符和參數之間需要放置一個空格,一元運算符、強制類型轉換和參數之間不放置空格。關鍵字之后圓括號之前需要放置一個空格.

  void *ptr = &value + 10 * 3;
 NewType a = (NewType)b;
 for (int i = 0; i < 10; i++) {
     doCoolThings();
 }

7.長的字面值應被拆分為多行。

NSArray *theShit = @[
    @"Got some long string objects in here.",
    [AndSomeModelObjects too],
    @"Moar strings."
];
NSDictionary *keyedShit = @{
@"this.key": @"corresponds to this value",
@"otherKey": @"remoteData.payload",
@"some": @"more",
@"JSON": @"keys",
@"and": @"stuff",
};

命名

命名是編程中最基本的技能,我們給變量、函數、類、包等等命名。給他們以名字,讓他們有意義,既能表示他們到底是做什么的,也能將其與其他變量區別開來。而通過,語言的發展史,我們也能夠看到“方便編程人員理解和使用”一直都是編程語言發展的動力之一,而命名則是其最最核心的環節。像人一樣娶一個好名字至關重要,“丁當”總比“狗蛋”來的好聽。 為什么要命名?命名代表著抽象,我們使用名字將一些沒必要關系的細節隱去,減少我們自己的記憶成本,也更加方便我們理解。用過C語言的人都知道,一個變量名最終會轉化成類似于~~~0x11111111~~~之類的地址,相比去理解和記憶這些地址,用一個更加抽象的變量名來代表這些地址。無論從理解還是記憶上都要方便的。

命名一定要“名副其實”,盡可能使用有意的名稱,而且這個意義和指稱的變量真實意義相關。

盡量不要出現沒有任何意義的命名類似于下述形式的命名:

    int a = 1;
    int b = 3;
    CGPoint point = CGPointMake(a,b);

如果換成下面的形式是不是可讀性強了很多:

    int startX = 1;
    int startY = 3;
    CGPoint startPoint = CGPointMake(startX,startY);

命名首字母大寫,其他命名首字母小寫。并且采用駝峰格式分割單詞。

例如:BWTest

使用能夠讀出來的名稱

人類長于記憶和使用單詞。大腦中的相當一部分就是用來容納和處理單詞的。單詞如果能夠讀的出來,則非常方便我們閱讀和理解。

錯誤的示例: genymdhms (生成日期,年、月、日、時、分、秒)

正確的實例: generationTimeStamp

使用可搜索的名稱

單字母名稱和數字常量有一個問題,就是很難在一大篇文字中找出來。試想一下,你找~~~MAX_CLASSES_PER_STUDENT~~~容易還是找數字7容易。

文件名

文件名反映出了其實現了什么類(包括大小寫),你需要遵循所參與醒目的約定。

文件的擴展名及其意義如下:

Objective-C 編碼建議

類別的擴展名以“被擴展的類名+自定義命名部分組成”

例如:NSSstring+Utils.h

縮略詞

雖然方法命名不應使用縮略詞,然而有些縮略詞在過去被反復的使用,所以使用這些縮略詞能更好的的表達代碼的含義。下表列出了Cocoa可接受的縮略詞。

Objective-C 編碼建議

以下是一些常用的首字母縮略詞:ASCII,PDF,XML,HTML,URL,RTF,HTTP,TIFF,JPG,PNG,GIF,LZW,ROM,RGB,CMYK,MIDI,FTP…

宏定義全部字母大寫,例如:#define BW_DEBUG 1

常量定義,字符串定義以小寫字母 k 開頭,隨后首字母大寫

static NSString* const kBWBarTitle = @"動態";

如果要定義常量使用static const優于宏定義,前者會進行類型檢查

因為OC沒有命名空間的概念,所以使用前兩個或者多個字母來表示命名空間,例如”NSObject中的NS”,我們也使用自己的命名空間。比如

紅點中使用了VAS:VASAddValueInfo...
錢包中使用了QW:QWApplication....

注釋

讓代碼自注釋,不要依賴注釋來解釋自己的設計或者編碼意圖。除了特殊情況外,代碼中不要有多余的注釋。

函數

函數長度不要超過50行,小函數要比大函數可閱讀性和可復用性強。

零元函數最好,一元函數也不錯,二元函數擔心了,三元函數有風險,高于三元需重構。函數的參數越多,引起其變化的因素就越多。越不利于以后的修改。

不知道當你看到如下形式的函數的時候,是什么想法:

- (void)RequestGetLocation:(int)lat lon:(int)lon alt:(int)alt isMars:(BOOL)yn bJiejingSOSO:(BOOL)bJiejingSOSO;

盡量少的寫有副作用的函數

盡量不要出現火車鏈式的命名,如果可以盡量使用過程變量替代。

反例例如:

_needLogoutAccount = [[[[BWAppSetting GetInstance] appSetting] valueForKey:NeedLogoutAccounts] retain];

考慮如果改成下述模樣,是不是可讀性一下子提高了很多:

BWAppSetting* shareSetting = [BWAppSetting GetInstance];
BWLockDictionary* defaultSettings = [shareSetting appSetting];
_needLogoutAccount = [[defaultSettings valueForKeyPath:NeedLogoutAccounts] retain];

調用時所有參數應該在同一行

[myObject doFooWith:arg1 name:arg2 error:arg3];

或者每行一個參數,以冒號對齊:

[myObject doFooWith:arg1
               name:arg2
              error:arg3];

對于參數過多的函數,盡量使用后面一種對其方式。

不要使用下面的縮進風格:

[myObject doFooWith:arg1 name:arg2  // some lines with >1 arg
              error:arg3];
[myObject doFooWith:arg1
               name:arg2 error:arg3];
[myObject doFooWith:arg1
          name:arg2  // aligning keywords instead of colons
          error:arg3];

如果對傳入參數進行數據保護盡量不要用~~~if(!objc)~~~,使用斷言來處理。

- (void) sendArgs:(NSDictionary*)args {
        NSAssert(args, @"args is nil");
        .....   
}

方法參數名前一般使用的前綴包括“the”、“an”、“new”。

示例:

- (void)         setTitle:           (NSString *)   aTitle;
- (void)         setName:            (NSString *)   newName;
- (id)          keyForOption:         (CDCOption *)  anOption
- (NSArray *)      emailsForMailbox:       (CDCMailbox *) theMailbox;
- (CDCEmail *)      emailForRecipients:      (NSArray *)    theRecipients;

Block相關

在block中使用到self變量的時候,一定要先weak再strong.

__weak typeof(self) weakSelf = self;
[self doABlockOperation:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
}];

控制結構

順序結構

分支結構

if-else結構超過四層的時候,要考慮重構。多層的ifelse結構極其難維護。

當需要滿足一定條件時才執行某項操作時,最左邊緣應該是愉快路徑代碼。不要將愉快路徑代碼內嵌到if語句中。多個return是正常合理的。

良好的風格:

- (void) someMethod {
  if (![someOther boolValue]) {
      return;
  }
  //Do something important
}

反面教材:

- (void) someMethod {
  if ([someOther boolValue]) {
      //Do something important
  }
}

所有的邏輯塊必須使用花括號包圍,即使條件體只需編寫一行代碼也必須使用花括號。

良好的風格:

if (!error) {
    return success;
}

反面教材:

if (!error)
    return success;
...
if (!error) return success;

循環結構

遍歷可變容器之前,需要復制該容器,遍歷該容器的Copy.

//typeof(self.cells) is NSMutableArray
NSArray* cellArrays = [self.cells copy];
for(UITableViewCell* cell in cellArrays) {
        ...
}

盡量不要使用異常,尤其是不要將異常做為業務邏輯的一部分,在異常中嘗試進行災難恢復。

類與對象

明確指定構造函數

注釋并且明確指定你的類的構造函數。

對于需要繼承你的類的人來說,明確指定構造函數十分重要。這樣他們就可以只重寫一個構造函數(可能是幾個)來保證他們的子類的構造函數會被調用。這也有助于將來別人調試你的類時,理解初始化代碼的工作流程。 ###重載指定構造函數

當你寫子類的時候,如果需要 init… 方法,記得重載父類的指定構造函數。

如果你沒有重載父類的指定構造函數,你的構造函數有時可能不會被調用,這會導致非常隱秘而且難以解決的 bug。

重載 NSObject的方法

如果重載了 NSObject 類的方法,強烈建議把它們放在 @implementation 內的起始處,這也是常見的操作方法。

通常適用(但不局限)于init…,copyWithZone:,以及dealloc方法。所有init…方法應該放在一起,copyWithZone: 緊隨其后,最后才是dealloc 方法

初始化

不要在 init 方法中,將成員變量初始化為 0 或者 nil;毫無必要。

現代的 Ojbective-C 代碼通過調用 alloc 和 init 方法來創建并 retain 一個對象。由于類方法 new 很少使用,這使得有關內存分配的代碼審查更困難。

保持init函數簡潔,不要讓init函數成為千行的大函數,當超過50行的時候,適當考慮分拆一下。

良好的風格實例:

- (void) commonInit
{
    _rightAppendImageView = [UIImageView new];
    [self.contentView addSubview:_rightAppendImageView];
}
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (!self) {
        return self;
    }
    [self commonInit];
    return self;
}

UIView的子類初始化的時候,不要進行任何布局操作。布局操作在LayoutSubViews里面做。

UIView的子類布局必須在layoutSubViews里面進行,需要布局的時候調用~~~setNeedLayout~~~來告訴系統,需要重新布局該View,不要直接調用~~~layoutSubViews~~~

保持公共 API 簡單

"保持類簡單;避免 “廚房水槽(kitchen-sink)” 式的 API。如果一個函數壓根沒必要公開,就不要這么做。用私有類別保證公共頭文件整潔。"

與 C++ 不同,Objective-C 沒有方法來區分公共的方法和私有的方法 – 所有的方法都是公共的(譯者注:這取決于 Objective-C 運行時的方法調用的消息機制)。因此,除非客戶端的代碼期望使用某個方法,不要把這個方法放進公共 API 中。盡可能的避免了你你不希望被調用的方法卻被調用到。這包括重載父類的方法。對于內部實現所需要的方法,在實現的文件中定義一個類別,而不是把它們放進公有的頭文件中。

// GTMFoo.m
#import "GTMFoo.h"
@interface GTMFoo (PrivateDelegateHandling)
- (NSString *)doSomethingWithDelegate;  // Declare private method
@end
@implementation GTMFoo(PrivateDelegateHandling)
...
- (NSString *)doSomethingWithDelegate {
  // Implement this method
}
...
@end

在OC2.0以后,你可以在實現文件中使用,類擴展來生命你的私有類別:

@interface GMFoo () { ... }

每個文件中只創建或者實現一個類。同一個文件中不要存在多個類。

Protocol單獨用一個文件來創建。盡量不要與相關類混在一個文件中。

類的私有變量以”_“開頭。

創建私有變量,份兩種情況。 第一種情況子類需要繼承的,在頭文件中定義:

// BWTest.h
@interface BWTest : NSObject
{
    NSString* _name;
}

第二種情況,不需要子類繼承的,在實現文件中以Category的方式定義:

// BWTest.m @interface BWTest () { NSString* _name; }
@implementation BWTest
...
@end

公有變量在一般使用屬性的方法定義 @property (….) … 

使用委托模式,設置delegate的時候,在ARC下使用 weak ;在MRC下使用 retain ,并且在dealloc中將其指針置空。

外部引用對象,外部不會發生set操作的對象,比如在創建界面元素的時候,使用readonly屬性。

@interface BWView : UIView
@property (nonatomic, strong, readonly) UIView* backgoundView;
@end
@implementation BWView
@end

在類定義中使用到自己定義的類的時候,盡量不要在頭文件中引入自己定義的類的同文件,使用 @class 替換。在實現文件中引入相應頭文件。

例如:

//BWTest.h @class BWDataCenter; @interface BWTest : NSObject @property (nonatomic, strong) BWDataCenter* dataCenter; @end

//BWTest.m

import “BWDataCenter.h”

@implementation BWTest @end

如果一個類只是DTO(data transfer object),只是作為數據傳輸使用,可以不用引入使用的自定義的類的頭文件,只是用 @class ,表明相應的自定義的類型。

對于DTO類型的對象,在給其成員變量設置值的時候可以考慮使用KVC,實現下述函數:

- (void) setValue:(id)value forKey:(NSString *)key
{
    if ([key isEqualToString:kRedDotAppInfoPath]) {
        ....
    } else if ...
    ....
}
- (id) valueForKey:(NSString *)key {
  ....
}

點標記語法

屬性和冪等方法(多次調用和一次調用返回的結果相同)使用點標記語法訪問,其他的情況使用方括號標記語法。 良好的風格:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

反面實例:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

Cocoa相關

每個NSObject都有其生命周期,要在其生命周期的合適的時機做合適的事情。

例如:在初始化的時候,進行變量初始化,在銷毀的時候,銷毀變量等等。

盡量不要在界面布局的寫任何死數字

錯誤的示范:

CGFloat delta = SYSTEM_VERSION >= 7.0 ? 0.0f : -14.0f;
newFrame = CGRectMake(245 + delta,
    (self.frame.size.height - tipNewSize.height)/2,
    tipNewSize.width,
    tipNewSize.height);
       dotFrame = CGRectMake(258.0 + delta,  (self.frame.size.height - tipDotSize.height)/2,
    tipDotSize.width,
    tipDotSize.height);
       iconFrame = CGRectMake(245 + delta,
     (self.frame.size.height - tipIconSize.height)/2,
     tipIconSize.width,
     tipIconSize.height);
       numFrame = CGRectMake(245+delta, (self.frame.size.height - tipNumSize.height)/2, tipNumSize.width, tipNumSize.height);

正確的示范:

CGFloat cellHeight = CGRectGetHeight(self.frame);
CGFloat cellWidth = CGRectGetWidth(self.frame);
CGRect numFrame = CGRectZero;
numFrame.size = CGSizeMake(cellWidth,cellHeight);
...

布局時盡量使用相對布局,比如使用子View在父View中的相對位置。

在使用UITableView和UITableViewCell的時候一定要考慮到cell被復用的情況,在合適的時機對重用的cell進行清除操作。

為UITableViewCell功能或者子View的時候有限考慮子類化。盡量不要使用在delegate中為Cell添加View。子類化,利于Cell重用和對cell內新添加的子View的布局。

良好的風格示例:

@interface BWSettingCell : UITableViewCell
@property (nonatomic, strong, readonly) UIImageView* rightAppendImageView;
@end
@implementation BWSettingCell
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (!self) {
        return self;
    }
    _rightAppendImageView = [UIImageView new];
    [self.contentView addSubview:_rightAppendImageView];
    return self;
}
- (void) layoutSubviews
{
    [super layoutSubviews];
    CGSize rightImageSize = _rightAppendImageView.image.size;
    _rightAppendImageView.frame = CGRectMake(CGRectGetWidth(self.frame) - rightImageSize.width,
                                             (CGRectGetHeight(self.frame) - rightImageSize.height) /2,
                                             rightImageSize.width,
                                             rightImageSize.height);
}
@end

反面教材:

- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* const settingCellIdentify = @"settingCellIdentify";
    UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:settingCellIdentify];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:settingCellIdentify];
    }
    static int kSettingCellSubViewTag = 90001;
    //非常錯誤的地方,盡量不要這樣寫
    [cell.contentView removeAllSubviews];
    UIImageView* rightAppendingView = [UIImageView new];
    rightAppendingView.image = nil;
    rightAppendingView.frame = CGRectMake(230, 8, 30, 30);
    [cell.contentView addSubview:rightAppendingView];
    return cell;
}

設計模式相關

使用設計模式的最基本原則,除非你明確知道自己要做件什么事情,而且知道使用特定設計模式帶來的影響,否則不要刻意的使用設計模式。

單例模式

創建一個單例模式可以使用dispatch_once

+ (instancetype)defaultManager
{
    if (!_defaultManager) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _defaultManager = [[FlappyEggManager alloc] init];
        });
    }
    return _defaultManager;
}

觀察者模式

如果只是單純的傳遞數據,不要使用觀察者模式,容易導致邏輯鏈斷裂。

參考資料

1.《 Clean Code

2.《 編寫可閱讀代碼的藝術

3.《 Google Objective-C Style Guide

4.《 Introduction to Coding Guidelines for Cocoa

5.《 iOS應用開發最佳實踐系列一:編寫高質量的Objective-C代碼

想要閱讀更多內容,歡迎關注微信公共賬號IOS_Tips。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!