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

      本文由海水的味道編譯整理,轉載請註明譯者和出處,請勿用於商業用途!

點標記語法

屬性和冪等方法(屢次調用和一次調用返回的結果相同)使用點標記語法訪問,其餘的狀況使用方括號標記語法。php

良好的風格:html

view.backgroundColor = [UIColor orangeColor];java

[UIApplication sharedApplication].delegate;ios

不良的風格:git

[view setBackgroundColor:[UIColor orangeColor]];程序員

UIApplication.sharedApplication.delegate;github

間距

二元運算符和參數之間須要放置一個空格,一元運算符強制類型轉換和參數之間不放置空格。關鍵字以後圓括號以前須要放置一個空格。objective-c

void *ptr = &value + 10 * 3;api

NewType a = (NewType)b;數組

 

for (int i = 0; i < 10; i++) {

    doCoolThings();

}

數組和字典類型的字面值的方括號兩邊各放置一個空格。

NSArray *theShit = @[ @1, @2, @3 ];

字典字面值的鍵和冒號之間沒有空格,冒號和值之間有一個空格。

NSDictionary *keyedShit = @{ GHDidCreateStyleGuide: @YES };

C函數聲明中,左括號的前面不保留空格,而且函數名應該像類同樣帶有命名空間標識。

良好的風格:

void RNCwesomeFunction(BOOL hasSomeArgs);

長的字面值應被拆分爲多行。

良好的風格:

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",

};

每一行代碼使用4個空格縮進。不使用tab縮進。下圖是在Xcode的Preferences進行縮進設置的截圖。

 

 

方法簽名以及其餘關鍵字(if/else/switch/while等)後面跟隨的左花括號老是和語句出現於同一行,而右花括號獨佔一行。

良好的風格:

if (user.isHappy) {

//Do something

}

else {

//Do something else

}

若是一個方法內有多個功能區域,可使用空行分隔功能區域。

每一行代碼不要超過100個字符。

每個方法以前都有一個99字符寬的註釋行,註釋行相對於使用空行更能提升代碼的辨識度,當一行代碼很長的時候,註釋行也起到了越界檢測的做用。註釋行:

///////////////////////////////////////////////////////////////////////////////////////////////////

條件語句

當須要知足必定條件時才執行某項操做時,最左邊緣應該是愉快路徑代碼。不要將愉快路徑代碼內嵌到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;

三元運算符

長的三元運算符應使用圓括號括起來。三元運算符僅用於賦值和作參數。

Blah *a = (stuff == thing ? foo : bar);

合併的nil三元運算符應該儘可能避免。

不良的風格:

Blah *b = thingThatCouldBeNil ?: defaultValue;

多分支條件應該使用if語句或重構爲實例變量。

良好的風格:

result = a > b ? x : y;

不良的風格:

result = a > b ? x = c > d ? c : d : y;

異常和錯誤處理

不要在流控制語句中使用異常(NSException)。

異常僅用於代表程序員的錯誤。

爲了代表一個錯誤,使用NSError *

當一個方法經過引用返回一個錯誤參數,應該檢測返回值的狀態,而不是錯誤參數的狀態。

良好的風格:

NSError *error;

if (![self trySomethingWithError:&error]) {

    // Handle Error

}

不良的風格:

NSError *error;

[self trySomethingWithError:&error];

if (error) {

    // Handle Error

}

在方法執行成功的狀況下賦值非Null值給錯誤參數,會使路徑跳轉到假條件分支(隨後程序奔潰)。

代理

除了繼承一個類或實現一個協議,不然在頭文件中僅使用類聲明@class指令,不用#import導入類頭文件。

若是一個delegate只有幾個方法,好比只是提交和取消,推薦使用block編寫動做響應代碼。

使用block仍是delegate編寫回調代碼遵循如下幾點:(詳見參考連接[8]

Ø   若是對象存在多個不一樣事件,則應該使用代理模式編寫各事件的處理代碼

Ø   若是對象是單例,應該使用block,而不是代理。

Ø   若是對象是爲了其餘的信息而進行回調,則使用代理。

Ø   代理更多的是面向於過程,而block則更多的面向於結果。

因爲代理方法的聲明通常都很長,因此必須將代理對象和其餘的協議對象放在實例變量定義的下面,不然實例變量定義的對齊方式將會被打亂掉。

當須要實現多個協議的時候,將每個協議名拆分到單獨的行。

良好的風格:

@interface CustomModelViewController : TTViewController <

  TTModelDelegate,

  TTURLRequestDelegate

> {

方法

一個方法的命名首先描述返回什麼,接着是什麼狀況下被返回。方法簽名中冒號的前面描述傳入參數的類型。如下類方法和實例方法命名的格式語法:

[object/class thing+condition];

[object/class thing+input:input];

[object/class thing+identifer:input];

Cocoa命名舉例:

realPath    = [path     stringByExpandingTildeInPath];

fullString  = [string   stringByAppendingString:@"Extra Text"];

object      = [array    objectAtIndex:3];

// 類方法

newString   = [NSString stringWithFormat:@"%f",1.5];

newArray    = [NSArray  arrayWithObject:newString];

良好的自定義方法命名風格:

recipients  = [email    recipientsSortedByLastName];

newEmail    = [CDCEmail emailWithSubjectLine:@"Extra Text"];

emails      = [mailbox  messagesReceivedAfterDate:yesterdayDate];

當須要獲取對象值的另外一種類型的時候,方法命名的格式語法以下:

[object adjective+thing];

[object adjective+thing+condition];

[object adjective+thing+input:input];

良好的自定義方法命名風格:

capitalized = [name    capitalizedString];

rate        = [number  floatValue];

newString   = [string  decomposedStringWithCanonicalMapping];

subarray    = [array   subarrayWithRange:segment];

方法簽名儘可能作到含義明確。

不良的風格:

-sortInfo  // 是返回排序結果仍是給info作排序

-refreshTimer  // 返回一個用於刷新的定時器仍是刷新定時器

-update  // 更新什麼,如何更新

良好的風格:

-currentSortInfo      // "current" 清楚地修飾了名詞SortInfo

-refreshDefaultTimer  // refresh是一個動詞。

-updateMenuItemTitle  // 一個正在發生的動做

方法類型修飾符+/-後要放置一個空格,各參數名之間也要放置一個空格。

良好的風格:

- (void)setExampleText:(NSString *)text image:(UIImage *)image;

若是方法的命名特別長,將方法名拆分紅多行。

良好的風格:

color = [NSColor colorWithCalibratedHue: 0.10

                               saturation: 0.82

                              brightness: 0.89

                                    alpha: 1.00];

不要將私有的實例變量和方法聲明在頭文件中,應將私有變量和方法聲明在實現文件的類擴展內。

不良的風格:

//MyViewController.h文件

@interface MyViewController : UIViewController<

 UITalbeViewDataSource,

 UITableViewDelegate> {

 @private:

  UITableView *_myTableView;  // 私有實例變量

}

// 內部使用的屬性

@property (nonatomic,strong) NSNumber *variableUsedInternally;

- (void)sortName;  // 只用於內部使用的方法

@end

良好的風格:

//MyViewController.m文件使用類擴展

@interface MyViewController()<

 UITalbeViewDataSource,

 UITableViewDelegate> {

  UITableView *_myTableView;

// 外部須要訪問的實例變量聲明爲屬性,不須要外部訪問的聲明爲實例變量

  NSNumber * variableUsedInternally;

}

// Xcode4.3開始,能夠不寫方法的前置聲明,Interface BuilderStoryboard仍然能夠找到方法的定義

@end

構造函數一般應該返回實例類型而不是id類型

參數

方法參數名前通常使用的前綴包括「the」、「an」、「new」。

良好的風格:

- (void)       setTitle:           (NSString *)   aTitle;

- (void)       setName:            (NSString *)   newName;

- (id)         keyForOption:       (CDCOption *)  anOption

- (NSArray *)  emailsForMailbox:   (CDCMailbox *) theMailbox;

- (CDCEmail *) emailForRecipients: (NSArray *)    theRecipients;

變量

變量的命令應儘可能作到自描述。除了在for()循環語句中,單字母的變量應該避免使用(如i,j,k等)。通常循環語句的當前對象的命名前綴包括「one」、「a/an」。對於簡單的單個對象使用「item」命名。

良好的風格:

for (i = 0; i < count; i++) {

    oneObject = [allObjects objectAtIndex: i];

    NSLog (@"oneObject: %@", oneObject);

}

      

NSEnumerator *e = [allObjects objectEnumerator];

id item;

while (item = [e nextObject])

      NSLog (@"item: %@", item);

指針變量的星號指示符應該緊靠變量,好比NSString *text,而不是NSString* textNSString * text

儘可能的使用屬性而非實例變量。除了在初始化方法(initinitWithCoder:等)、dealloc方法以及自定義settergetter方法中訪問屬性合成的實例變量,其餘的狀況使用屬性進行訪問。

良好的風格:

@interface RNCSection: NSObject

@property (nonatomic) NSString *headline;

@end

不良的風格:

@interface RNCSection : NSObject {

    NSString *headline;

}

當你使用@synthesize指令時,編譯器會自動爲你建立一個下劃線_開頭的的實例變量,因此不須要同時聲明實例變量和屬性。

不良的風格:

@interface RNCSection : NSObject {

    NSString *headline;

}

@property (nonatomic) NSString *headline;

@end

良好的風格:

@interface RNCSection: NSObject

@property (nonatomic) NSString *headline;

@end

不要使用@synthesize除非是編譯器須要。注意在@protoco協議中的@optional可選屬性必須被顯式地使用@synthesize指令合成屬性。

縮略詞

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

縮略詞

含義和備註

alloc

分配,撥出

alt

輪流,交替

app

應用程序。好比NSApp表示全局程序對象。

calc

計算

dealloc

銷燬、析構

func

函數

horiz

水平的

info

信息

init

初始化

max

最大的

min

最小的

msg

消息

nib

Interface Builder文檔

pboard

黏貼板(僅對常量)

rect

矩形

temp

臨時、暫時

vert

垂直的

如下是一些經常使用的首字母縮略詞

ASCII

PDF

XML

HTML

URL

RTF

HTTP

TIFF

JPG

PNG

GIF

LZW

ROM

RGB

CMYK

MIDI

FTP

命名

方法和變量的命令應該儘量作到自描述。

良好的風格:

UIButton *settingsButton;

不良的風格:

UIButton *setBut;

對於NSStringNSArrayNSNumberBOOL類型,變量的命名通常不須要代表其類型。

良好的風格:

NSString       *accountName;

NSMutableArray *mailboxes;

NSArray        *defaultHeaders;

BOOL             userInputWasUpdated;

不良的風格:

NSString        *accountNameString;

NSMutableArray *mailboxArray;

NSArray        *defaultHeadersArray;

BOOL             userInputWasUpdatedBOOL;

若是變量不是以上基本經常使用類型,則變量的命名就應該反映出自身的類型。但有時僅須要某些類的一個實例的狀況下,那麼只須要基於類名進行命名。

NSImage               *previewPaneImage

NSProgressIndicator  *uploadIndicator

NSFontManager        *fontManager;       // 基於類名命名

大部分狀況下,NSArrayNSSet類型的變量只須要使用單詞複數形式(好比mailboxes),沒必要在命名中包含「mutable」。若是複數變量不是NSArrayNSSet類型,則須要指定其類型。

良好的風格:

NSDictionary * keyedAccountNames;

NSDictionary * messageDictionary;

NSIndexSet   * selectedMailboxesIndexSet;

因爲Objective-C不支持名字空間,爲了防止出現命名空間的衝突,在類名和常類型變量名前添加一個由三個大寫的字母組成的前綴(如RNC),對於Core Data實體名則能夠忽略此規則。若是你子類化了標準的Cocoa類,將前綴和父類名合併是一個很好的作法。如繼承UITableView的類可命名爲RNCTableView

常類型變量名的書寫風格採用駝峯式大小寫(第一個單詞的首字母小寫,其他單詞的第一個字母大寫。如firstName而不是first_namefirstname。),並使用關聯的類名做爲其命名前綴,

推薦的作法:

static const NSTimeInterval RNCArticleViewControllerNavigationFadeAnimationDuration = 0.3;

不推薦的作法:

static const NSTimeInterval fadetime = 1.7;

下劃線

使用屬性的時候,實例變量應該使用self.進行訪問和設值。局部變量的命令不要包含下劃線。實例變量的命名必須使用下劃線_做爲前綴,這樣能夠縮小Xcode自動完成的選項取值範圍。

註釋

在須要的時候,註釋可對代碼作必要的解釋。更新代碼時必定要更新註釋,防止對代碼形成誤解。

使用javadoc風格的文檔註釋語法。註釋的第一行是對註釋API的總結,隨後的註釋行是對代碼更多細節的解釋。

良好的風格:

/**

 * The maximum size of a download that is allowed.

 *

 * If a response reports a content length greater than the max * will be cancelled. This is helpful for preventing excessive memory usage.

 * Setting this to zero will allow all downloads regardless of size.

 *

 * @default 150000 bytes

 */

@property (nonatomic) NSUInteger maxContentLength;

initdealloc

dealloc方法應該被放置在實現方法的頂部,直接在@synthesize@dynamic語句以後。init方法應該被放置在dealloc方法的下面。

init方法的結構看上去應該像這樣:

- (instancetype)init {

    self = [super init]; // or call the designated initalizer

    if (!self) {

        return nil;

    }

      // Custom initialization

    return self;

}

字面值

對於NSStringNSDictionaryNSArrayNSNumber類,當須要建立這些類的不可變實例時,應該使用這些類的字面值表示形式。使用字面值表示的時候nil不須要傳入NSArrayNSDictionary中做爲字面值。這種語法兼容老的iOS版本,所以能夠在iOS5或者更老的版本中使用它。

良好的風格:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];

NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};

NSNumber *shouldUseLiterals = @YES;

NSNumber *buildingZIPCode = @10018;

不良的風格:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];

NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];

NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];

NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];

如非必要,避免使用特定類型的數字(相較於使用5.3f,應使用5.3)。

CGRect函數

相較於使用結構體輔助函數(如CGRectMake()函數),優先使用C99結構體初始化語法。

  CGRect rect = {.origin.x = 3.0, .origin.y = 12.0, .size.width = 15.0, .size.height = 80.0 };

當訪問CGRect結構體的xywidthheight成員時,應使用CGGeometry函數,不直接訪問結構體成員。蘋果對CGGeometry函數的介紹:

All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

良好的風格:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);

CGFloat y = CGRectGetMinY(frame);

CGFloat width = CGRectGetWidth(frame);

CGFloat height = CGRectGetHeight(frame);

不良的風格:

CGRect frame = self.view.frame;

CGFloat x = frame.origin.x;

CGFloat y = frame.origin.y;

CGFloat width = frame.size.width;

CGFloat height = frame.size.height;

常量

 優先使用常類型變量,而不是內嵌的字符串字面值或數字,由於常類型變量能很容易的複用經常使用的變量值(如π),同時能夠快速地修改值而無需查找替換。常類型變量應該聲明爲static類型,不要使用#define,除很是類型變量被做爲宏使用。

良好的風格:

static NSString * const RNCAboutViewControllerCompanyName = @"The New York Times Company";

static const CGFloat RNCImageThumbnailHeight = 50.0;

不良的風格:

#define CompanyName @"The New York Times Company"

#define thumbnailHeight 2

枚舉類型

當使用enum關鍵字時,推薦使用蘋果最新引入的固定基礎類型語法,由於這將得到強類型檢查與代碼完成功能。SDK如今包含了一個固定基礎類型的宏——NS_ENUM()。

NS_ENUM是在iOS6中開始引入的,爲了支持以前的iOS版本,使用簡單的內聯方法:

#ifndef NS_ENUM

#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type

#endif

良好的風格:

typedef NS_ENUM(NSInteger, RNCAdRequestState) {

    RNCAdRequestStateInactive,

    RNCAdRequestStateLoading

};

私有屬性

私有屬性應該被聲明在實現文件的類擴展中(即匿名的category)。不要將私有屬性聲明在命名的category(如RNCPrivateprivate),除非是擴展其餘類。

良好的風格:

@interface NYTAdvertisement ()

 

@property (nonatomic, strong) GADBannerView *googleAdView;

@property (nonatomic, strong) ADBannerView *iAdView;

@property (nonatomic, strong) UIWebView *adXWebView;

 

@end

圖片的命名

圖片的命名應該保持一致,以圖片的用途描述做爲圖片文件名。文件名的命名使用駝峯式大小寫風格,文件名後可跟隨一個自定義的類名或者是自定義的屬性名(若是有屬性名)、也能夠再跟上顏色描述以及/或者位置、圖片的最終狀態。

良好的風格:

RefreshBarButtonItem / RefreshBarButtonItem@2x RefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x

ArticleNavigationBarWhite / ArticleNavigationBarWhite@2x ArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.

被用做類似用途的圖片應該使用一個圖片文件夾進行分開管理。

布爾類型

由於nil被解析爲了NO,因此和nil做比較沒有任何的必要。不要將變量和YES直接比較,由於YES被定義爲1BOOL類型是8位的unsigned int,即BOOL的值不只僅是10

良好的風格:

if (!someObject) {

}

不良的風格:

if (someObject == nil) {

}

對於一個BOOL值:兩種最佳實踐:

if (isAwesome)

if (![someObject boolValue])

不良的風格:

if ([someObject boolValue] == NO)

if (isAwesome == YES) // Never do this.

若是一個BOOL類型的屬性名是一個形容詞,忽略屬性名的「is」前綴是容許的,但須要爲訪問器指定約定的方法名,好比:

@property (assign, getter=isEditable) BOOL editable;

單例

應該使用線程安全的模式建立共享的單例實例。

+ (instancetype)sharedInstance {

   static id sharedInstance = nil;

 

   static dispatch_once_t onceToken;

   dispatch_once(&onceToken, ^{

      sharedInstance = [[self alloc] init];

   });

 

   return sharedInstance;

}

   單例的另外一種作法,利用+ initialize方法。(JSONModel源碼43行:http://t.cn/8F7uBF4):

static JSONAPI* sharedInstance = nil;

+ (void)initialize {

    static dispatch_once_t once;

    dispatch_once(&once, ^{

        sharedInstance = [[JSONAPI alloc] init];

    });

}

附錄

Xcode主題

大部分的開發者都使用Xcode默認的字體顏色主題,其實好的主題不只能提升源代碼的辨識度,同時也增添了編碼的樂趣。如下是二款Xcode字體顏色主題連接:

 https://github.com/vinhnx/Ciapre-Xcode-theme

https://github.com/tursunovic/xcode-themes

代碼片斷

熟練使用代碼片斷庫能夠提升編碼的速度。Xcode4中,打開一個項目並讓右側編輯區可視,而後點擊右側底部面板的第四個{}圖標,打開代碼片斷庫,你能夠將經常使用的代碼拖入其中。如下是一個最新的開源代碼片斷庫連接:

https://github.com/mattt/Xcode-Snippets

code snippet library新建以下代碼,設定一個相似vci(view controller initialize含義)自動提示快捷鍵。當開始編寫ViewControllerView時,鍵入vci,將相應的代碼填入對應的位置。

#pragma mark - init Method

填入init,initWithFrame等方法

#pragma mark- View Life Cycle

填入viewdidload,viewdidappear等方法

#pragma mark- Override Parent Methods

填入updateViewConstraints,updateConstraint, prepareForSegue等方法

#pragma mark- SubViews Configuration

填入configureSubViewsconfigureTableView等方法,這裏的方法在init方法或view life cycle被調用

#pragma mark- Actions

填入-(IBAction)action:(id)sender[self addtarget:self action:@selector(action:)]動做指向的方法

#pragma mark- Public Methods

填入在.h外暴露的方法

#pragma mark- Private Methods

填入.m文件內部調用的方法

#pragma mark- DelegateDataSource, Callback Method

填入tableviewscrollview等代理方法

#pragma mark- Getter Setter

填入對@property初始化的方法

#pragma mark- Helper Method

填入一些幫助方法,若是使用擴展實現幫助方法不合適,則將幫助方法填在這裏

#pragma mark Temporary Area

填入一些你須要刪除,或者不肯定後面需不須要用,或者寫一寫備註之類的,相似代碼回收站含義


參考連接

[1] NYTimes Objective-C Style Guide https://github.com/NYTimes/objective-c-style-guide

[2] Coding Guidelines for Cocoahttps://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html

[3] iOS-view-frame-builderhttps://github.com/rsobik/ios-view-frame-builder/commit/0fa2d81762bc21619b1503d34b7d67160f4678f8

[4] Cocoa Style for Objective-C: Part I http://cocoadevcentral.com/articles/000082.php

[5] Cocoa Style for Objective-C: Part IIhttp://cocoadevcentral.com/articles/000083.php

[6] objective-c-conventionsIhttps://github.com/github/objective-c-conventions

[7] The official raywenderlich.com Objective-C style guide.https://github.com/raywenderlich/objective-c-style-guide#cgrect-functions

[8] Blocks or Delegationhttp://stablekernel.com/blog/blocks-or-delegation/

文檔修訂歷史

時間

備註

2015-10-18

添加code-snippet示例

2014-01-16

完善代理、條件語句、單例

2013-09-07

編寫高質量的Objective-C代碼

 

若是喜歡此文,記得點擊文章下方的推薦,以讓更多的人有所收穫。

文章中若有錯誤或不當之處望不吝指出,謝謝!

個人郵箱和微博: xdreamarshal@gmail.com, http://weibo.com/xdream86

本文的pdf下載地址

相關文章
相關標籤/搜索