http://bukkake.iteye.com/blog/695492
原文Google Objective-C Style Guide
iPhone項目新成立,也沒有編碼規範的積累,項目組本來是想拿老的C編碼規範套用的,但評審一下就發現問題多多,之後找到了Google的Objective-C的編碼規範,大家就先翻譯一下咯
聲明這是無版權翻譯,也不對任何錯誤負責,不保證文章的完整性,我到現在也認不全文法。
(大半年的事後,決定對這份文檔做重審,當然不是對修辭手法,而是處理内部的硬傷)
總覽
背景知識
Objective-C是一個C語言的擴充語言,非常動态,非常的“面向對象”,它被設計成既擁有複雜的面向對象設計理念又可以輕松使用與閱讀的語言,也是Mac OS X和iPhone開發的首選語言。
Cocoa是Mac OS X的主要應用架構,提供迅速開發各種功能的Mac OS X應用的Objective-C類集合。
Apple已經有一個很好也被廣泛接受的Objective-C的編碼規範,Google也有類似的C++編碼規範,這份Objective-C編碼規範很自然是Apple和Google的共同推薦的組合。是以,在閱讀本規範前,確定你已經閱讀了:
Apple's Cocoa Coding Guidelines
Google's Open Source C++ Style Guide
注意所有已在Google的C++編碼規範裡的禁用條款在Objective-C裡也适用,除非本文檔明确指出反對意見。
本文檔旨在描述可供可适用于所有Mac OS X代碼的Objective-C(包括Objective-C++)編碼規範和實踐。規範中的許多條款已經改進也不斷的被其他的項目和團隊所證明其指導性。Google的相關開源項目都遵守此規範。
Google已經釋出了一份作為Google Toolbox for Mac project (文檔中簡稱為GTM)的組成部分的遵守本規範的開源代碼。這份開放代碼也是本文很好的例證(原文看不太懂--Code meant to be shared across different projects is a good candidate to be included in this repository. )
注意本文不是Objective-C的教學指南,我們假設讀者已經了解語言。如果你是一個Objective-C的初學者或需要重溫,請閱讀The Objective-C Programming Language .
示例
人們說一個例子勝過千言萬語,是以就讓我們用例子來讓你感受以下編碼規範的風格,留間距,命名等等。
下例是一份頭檔案,展示對@interface 聲明正确的注釋和留間距
Java代碼

- // GTMFoo.h
- // FooProject
- //
- // Created by Greg Miller on 6/13/08.
- // Copyright 2008 Google, Inc. All rights reserved.
- //
- #import <Foundation/Foundation.h>
- // A sample class demonstrating good Objective-C style. All interfaces,
- // categories, and protocols (read: all top-level declarations in a header)
- // MUST be commented. Comments must also be adjacent to the object they're
- // documenting.
- //
- // (no blank line between this comment and the interface)
- @interface GTMFoo : NSObject {
- @private
- NSString *foo_;
- NSString *bar_;
- }
- // Returns an autoreleased instance of GMFoo. See -initWithString: for details
- // about the argument.
- + (id)fooWithString:(NSString *)string;
- // Designated initializer. |string| will be copied and assigned to |foo_|.
- - (id)initWithString:(NSString *)string;
- // Gets and sets the string for |foo_|.
- - (NSString *)foo;
- - (void)setFoo:(NSString *)newFoo;
- // Does some work on |blah| and returns YES if the work was completed
- // successfuly, and NO otherwise.
- - (BOOL)doWorkWithString:(NSString *)blah;
- @end
下例是一份源檔案,展示對接口的@implementation 的實作的正确注釋和留間隔。它也包括了主要方法如getters,setters,init ,和dealloc 的相關實作。
Java代碼

- //
- // GTMFoo.m
- // FooProject
- //
- // Created by Greg Miller on 6/13/08.
- // Copyright 2008 Google, Inc. All rights reserved.
- //
- #import "GTMFoo.h"
- @implementation GTMFoo
- + (id)fooWithString:(NSString *)string {
- return [[[self alloc] initWithString:string] autorelease];
- }
- // Must always override super's designated initializer.
- - (id)init {
- return [self initWithString:nil];
- }
- - (id)initWithString:(NSString *)string {
- if ((self = [super init])) {
- foo_ = [string copy];
- bar_ = [[NSString alloc] initWithFormat:@"hi %d", 3];
- }
- return self;
- }
- - (void)dealloc {
- [foo_ release];
- [bar_ release];
- [super dealloc];
- }
- - (NSString *)foo {
- return foo_;
- }
- - (void)setFoo:(NSString *)newFoo {
- [foo_ autorelease];
- foo_ = [newFoo copy];
- }
- - (BOOL)doWorkWithString:(NSString *)blah {
- // ...
- return NO;
- }
- @end
間隔與格式化
空格對tab鍵
僅使用空格,縮進兩個。
我們使用空格用于縮進,不要在編碼時使用tab鍵,你應該設定你的編輯器将tab鍵轉換成對應的空格。
行長度
代碼中的每行文本不要超過80個字元的長度。
盡管Objective-C正變得比C++更加繁冗,為了保持規範的互通性,我們還是決定保持80字元長度的限制。這比你想象中的更容易做到。
我們知道本條款是有争議的,但已有此多的代碼已經遵從了本條款,即使隻是保持一緻性也是一個充足的理由。
你可以在Xcode裡清楚地發現代碼中的違規,設定 Xcode > Preferences > Text Editing > Show page guide.(之後就可以在代碼編輯區域裡看到一條指定字元長度的訓示線了)
方法聲明與定義
留一個空格在-或+和傳回類型之間,但參數清單裡的參數之間不要留間隔。
方法應該寫成這樣:
Java代碼

- - (void)doSomethingWithString:(NSString *)theString {
- ...
- }
星号前的空格是可選的,你可以根據原來的代碼風格自行決定。
如果參數過多,推薦每個參數各占一行。使用多行的情況下,以參數前的冒号用于對齊:
(很遺憾這裡僅有Google Chrome浏覽器能看出是冒号對齊的......)
Java代碼

- - (void)doSomethingWith:(GTMFoo *)theFoo
- rect:(NSRect)theRect
- interval:(float)theInterval {
- ...
- }
當第一個關鍵字比其他的短時,後續行至少縮進四個空格。這樣你可以讓後續的關鍵字垂直對齊,而不是用冒号對齊:
Java代碼

- - (void)short:(GTMFoo *)theFoo
- longKeyword:(NSRect)theRect
- evenLongerKeyword:(float)theInterval {
- ...
- }
方法調用
方法調用的格式和方法聲明時的格式時一緻的,如果格式風格可選,遵從原有代碼的風格。
調用應該将所有參數寫在一行:
Java代碼

- [myObject doFooWith:arg1 name:arg2 error:arg3];
或者每個參數一行,用冒号對齊:
(對齊效果如前說明)
Java代碼

- [myObject doFooWith:arg1
- name:arg2
- error:arg3];
不要使用如下風格的寫法
Java代碼

- [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];
在聲明和定義時,如果因為關鍵字長度使就算有四個空格在前仍然無法用冒号對齊,那麼就讓後續行縮進四個空格而不是冒号對齊:
Java代碼

- [myObj short:arg1
- longKeyword:arg2
- evenLongerKeyword:arg3];
@public 和 @private
權限控制符@public 和@private 縮進一個空格.
類似C++的public,protected,private:
Java代碼

- @interface MyClass : NSObject {
- @public
- ...
- @private
- ...
- }
- @end
異常
每個異常标簽的@ 和開括号({ )寫在統一行,标簽和開括号間隔一個空格,同樣适用于@catch 語句。
如果你決定使用Objective-C的異常,那麼就按如下格式,不過你最好先看看Avoid Throwing Exceptions(見後)條款,了解為何你不應使用異常。
Java代碼

- @try {
- foo();
- }
- @catch (NSException *ex) {
- bar(ex);
- }
- @finally {
- baz();
- }
命名
命名規則對于維護代碼來說是非常重要的。Objective-C方法名往往很長,不過這也有好處,讀代碼就像讀散文(放屁),讓很多注釋變得毫無意義。
寫純Objective-C代碼時,我們基本上遵守标準Objective-C naming rules ,這些規則和C++的規則有很大的不同。比如Google的C++代碼規範推薦變量名構詞之間使用下劃線隔開,而本文檔推薦駝峰法,也是Objective-C社群的标準。
所有類,類别, 方法,以及變量如包括縮寫,則縮寫部分使用全大寫的縮寫(Initialisms )形式。這遵守Apple的标準,比如URL,TIFF以及EXIF。
當寫Objective-C++代碼時,情況就不是那麼單一了。許多項目需要實作帶一些Objective-C代碼的跨平台的C++APIs或者連接配接背景的C++代碼與前台的原生Cocoa代碼.這會造成兩種規範直接沖突。
我們的解決方法是根據方法/函數風格來決定。如果在@implementation 塊,就使用Objective-C的命名規則;如果在C++的方法實作塊,就使用C++的命名規則。避免了實體變量和本地變量在一個函數内命名規則沖突的情況,而這種情況是對可讀性的極大損害。
檔案命名
檔案名反映了它所包含的實作類的名字,遵從你所在項目的習慣。
檔案擴充名使用如下規則
.h | C/C++/Objective-C header file |
.m | Objective-C implementation file |
.mm | Objective-C++ implementation file |
.cc | Pure C++ implementation file |
.c | C implementation file |
類别的檔案名應該包含擴充類的名字,比如GTMNSString+Utils.h or GTMNSTextView+Autocomplete.h
Objective-C++
在一份源檔案裡,Objective-C++代碼遵守目前方法/函數的風格
為了盡量減少不同命名風格間的沖突,使用目前方法的風格。如果在@implementation塊,使用Objective-C命名規則,如果在C++類的函數實作塊,使用C++命名規則。
Java代碼

- // file: cross_platform_header.h
- class CrossPlatformAPI {
- public:
- ...
- int DoSomethingPlatformSpecific(); // impl on each platform
- private:
- int an_instance_var_;
- };
- // file: mac_implementation.mm
- #include "cross_platform_header.h"
- // A typical Objective-C class, using Objective-C naming.
- @interface MyDelegate : NSObject {
- @private
- int instanceVar_;
- CrossPlatformAPI* backEndObject_;
- }
- - (void)respondToSomething:(id)something;
- @end
- @implementation MyDelegate
- - (void)respondToSomething:(id)something {
- // bridge from Cocoa through our C++ backend
- instanceVar_ = backEndObject->DoSomethingPlatformSpecific();
- NSString* tempString = [NSString stringWithInt:instanceVar_];
- NSLog(@"%@", tempString);
- }
- @end
- // The platform-specific implementation of the C++ class, using
- // C++ naming.
- int CrossPlatformAPI::DoSomethingPlatformSpecific() {
- NSString* temp_string = [NSString stringWithInt:an_instance_var_];
- NSLog(@"%@", temp_string);
- return [temp_string intValue];
- }
類命名
類名(不包括類别和協定名)應該用大寫開頭的駝峰命名法。
在應用級别的代碼裡,盡量不要使用帶字首的類名。每個類都有相同的字首不能提高可讀性。不過如果是編寫多個應用間的共享代碼,字首就是可接受并推薦的做法了(型如 GTMSendMessage )。
類别命名
類别命名應該以兩三個字元的分類字首作為一個項目或通用的公用部分。類别名應該包含類的擴充。
舉個例子,如果我們想要建立一個基于NSString 的類别用于解析,我們應該把類别放到名字是GTMNSString+Parsing.h 的檔案裡,而類别本身的名字則是GTMStringParsingAdditions (是的,我們明白這個類别和其檔案名字不比對,但這個檔案可能還包括其他用于解析相關的類别)。類别的方法應該都使用一個字首(型如gtm_myCategoryMethodOnAString ),以防止Objective-C代碼在單名空間裡沖突。如果代碼本來就不考慮共享或在不同的位址空間(address-space),方法命名規則就沒必要恪守了。
Objective-C 方法命名
方法使用小寫開頭的駝峰法命名,每個參數都應該小寫開頭。
方法名應該盡可能讀起來像一句話,參數名就相對方法名的補充說明(比如convertPoing:fromRect: 或者replaceCharactersInRange:withString: ),詳見Apple's Guide to Naming Methods
存取(Accessor)方法應該一緻的在"取(getting)"的時候直接用變量名而不是在簽名加"get",如下:
Java代碼

- - (id)getDelegate; // AVOID
- - (id)delegate; // GOOD
不過這僅針對Objective-C代碼,C++代碼仍然遵循自己的代碼規範。
變量命名
變量名使用小寫開頭的駝峰法,類成員變量名最後加一個下劃線,比如:myLovalVariable, myInstanceVariable_ . 下面看不懂,原文Members used for KVO/KVC bindings may begin with a leading underscore iff use of Objective-C 2.0's @property isn't allowed.
一般變量命名
不要使用匈牙利命名法去标記文法,比如靜态類型或變量類型(int或pointer之類的)。使變量名盡量可以推測其用途屬性具有描述性。别一心想着少打幾個字母,讓你的代碼可以迅速被了解更加重要。比如:
Java代碼

- // AVOID
- int w;
- int nerr;
- int nCompConns;
- tix = [[NSMutableArray alloc] init];
- obj = [someObject object];
- p = [network port];
- // GOOD
- int numErrors;
- int numCompletedConnections;
- tickets = [[NSMutableArray alloc] init];
- userInfo = [someObject object];
- port = [network port];
實體變量
實體變量用駝峰法命名并字尾下劃線,就像usernameTextField_ . 然而我們允許一種例外就是用KVO/KVC去綁定一個實體變量而Objective-C 2.0 不能用(因為作業系統的限制)的情況,此時也可用字首下劃線的方法給每個變量命名。如果可以使用Objective-C 2.0,@property 和 @synthesize 提供了遵守命名規範的解決方法。
常量
常量(預定義,枚舉,局部常量等)使用小寫k開頭的駝峰法,比如kInvalidHandle , kWritePerm .
注釋
盡管寫起來很痛苦,但注釋仍然是使代碼保持可讀性的極端重要的方式。下面的條款描述了你應該注釋什麼以及在哪裡做注釋。但是記住:即使注釋是如此重要,最好的代碼還是自說明式的。起一個有意義的名字比起一個晦澀的名字然後在用注釋去解釋它好的多。
當你寫注釋的時候,記住注釋是寫給讀者,即下一個要了解你的代碼并繼續開發的人。"下一個"完全可能就是你自己。
同樣,所有C++編碼規範的條款仍然适用,隻是增加了一些條款,如下.
檔案注釋
每個檔案的開頭都是版權聲明,接着是檔案内容的描述。
法律聲明和作者欄
每個檔案都應該包含如下資訊:
一份版權聲明(比如 Copyright 2008 Google Inc .)
許可版本 為項目選擇合适的許可版本(比如Apache 2.0, BSD, LGPL, GPL)
如果你把别人寫的檔案做了相當大的改動,就把自己添加到作者欄去。這樣别的開發者就友善聯系這個檔案的實際開發人員了。
聲明注釋
每個接口,類别,協定都必須伴随描述它的用途以及如何整合的注釋。
Java代碼

- // A delegate for NSApplication to handle notifications about app
- // launch and shutdown. Owned by the main app controller.
- @interface MyAppDelegate : NSObject {
- ...
- }
- @end
如果已經在檔案的頂部寫了接口的較長的描述,你也可以簡單的寫如"見檔案頂部的完整描述",當然要有這些注釋的順序安排。
此外public接口的每個方法都應該添加關于函數,參數,傳回值以及副作用的注釋。
文檔預設類都是同步的,如果類執行個體可以多線程通路,必須要加上額外的說明。
實作注釋
使用豎線引用變量或符号,而不是用引号。
這可以減少歧義,特别是當符号本身就是個常見的詞時,可能使句子顯得支離破碎,比如符号是"count":
Java代碼

- // Sometimes we need |count| to be less than zero.
或者是對于那些已經存在引号的情況
Java代碼

- // Remember to call |StringWithoutSpaces("foo bar baz")|
對象所有權
使指針所有權的模型盡可能清晰,當它屬于Objective-C的使用慣例時(不懂,原文是Make the pointer ownership model as explicit as possible when it falls outside the most common Objective-C usage idioms. )
執行個體變量指向NSObject派生類的對象時都假定是retain的,如果它們不是retain的則需要加上"weak"的文檔說明。對應的,實體變量如果标記上IBOutlets則是假定為非retain的,若實際上用了retain,就必須加上"strong"的說明。
當執行個體變量指向核心庫,C++或其他非Objective-C對象時,必須永遠用注釋說明是strong還是weak的。必須注意為了支援Objective-C對象裡的自動化C++對象的封裝是預設被關閉的的(這句話有歧義,原文是Be mindful that support for automatic C++ objects encapsulated in Objective-C objects is disabled by default),這裡 有說明。
strong和weak說明的文檔示例:
Java代碼

- @interface MyDelegate : NSObject {
- @private
- IBOutlet NSButton* okButton_; // normal NSControl
- IBOutlet NSMenu* myContextMenu_; // manually-loaded menu (strong)
- AnObjcObject* doohickey_; // my doohickey
- MyController* controller_; // so we can send msgs back (weak, owns me)
- // non-NSObject pointers...
- CWackyCPPClass* wacky_; // some cross-platform object (strong)
- CFDictionaryRef* dict_; // (strong)
- }
- @end
strong
對象會在類中retain
weak
對象不會在類中retain (比如一個委托)
Cocoa和Objective-C特性
成員變量應該定義為@private
Java代碼

- @interface MyClass : NSObject {
- @private
- id myInstanceVariable_;
- }
- // public accessors, setter takes ownership
- - (id)myInstanceVariable;
- - (void)setMyInstanceVariable:(id)theVar;
- @end
明确指定初始化
注釋并說明指定的初始化。
明确指定初始化對想要子類化你的類的時候時很重要的。那樣,子類化時隻需要做一個或多個初始化去保證初值即可。這也有助于在以後調試你的類時明了初始化流程。
重寫指定初始化
當重寫一個子類并需要init... 方法,注意要重寫父類的指定初始化方法。
如果你沒有正确重寫父類的指定初始化方法,你的初始化方法可能不會被調用,這會導緻很多微妙而難以排除的錯誤。
初始化
沒必要在初始化方法裡把變量初始化為0 或者nil ,這是多餘的。
所有新配置設定記憶體的對象内容都初始化為0(除了 isa ),是以不要在init 方法裡做無謂的重初始化為0的操作。
保持公有API簡明
保持你的類簡單,避免"廚房水槽"似的APIs,如果一個方法沒必要公開就不要公開。使用私有類别保證公開頭檔案的簡潔。
和C++不同,Objective-C無法區分公有私有方法,因為它全是公有的。是以,除非就是為了讓使用者調用所設計,不要把其他的方法放到公有API裡。這樣可以減少不期調用的可能性。這還包括重寫父類的方法。對于那些内部實作的方法,在實作檔案裡使用類别而不是将方法定義在公有頭檔案裡。
Java代碼

- // GTMFoo.m
- #import "GTMFoo.h"
- @interface GTMFoo (PrivateDelegateHandling)
- - (NSString *)doSomethingWithDelegate; // Declare private method
- @end
- @implementation GTMFoo(PrivateDelegateHandling)
- ...
- - (NSString *)doSomethingWithDelegate {
- // Implement this method
- }
- ...
- @end
在Objective-C 2.0之前,如果你在私有@interface 裡聲明了一個方法,但忘記在主@implementation 檔案裡實作了,編譯器不會有什麼反應(這是因為你沒有在不同的類别裡實作這些私有方法)。解決方案在是把函數寫到@implementation 裡并指明類别。
如果你用的是 Objective-C 2.0,你應該使用類擴充 而不是聲明私有類别,如下:
Java代碼

- @interface GMFoo () { ... }
如此就可以保證函數做了聲明但沒有在@implememtation 裡實作的時候編譯器會警報。
再者,"private"方法并不是真正的private,你可能會無意間重寫了父類的一個"private"方法,這回導緻bug的湧現。總的來說,私有方法應該使用更特别的名字以阻止子類化時并不期望的重寫。
最後,對于絕大多數類而言,Objective-C的類别是将@implelemtation做可了解的分塊,添加新的應用級别的功能的最佳途徑。比如,與其在你的項目裡随便找個類來實作字元串的"中間截斷"功能,不如建立一個新的NSString 類别。
#import 和 #include
用#import 導入Objective-C或Objective-C++頭檔案,用#include 導入C或C++頭檔案
根據頭檔案的語言去選擇合适的導入方式。
當導入的頭檔案使用Objective-C或Objective-C++語言時,使用#import .
當導入标準C或 C++頭檔案時,使用#include . 頭檔案應該使用自己的#define 重加載保護
有些Objective-C頭檔案沒有#define 重加載保護,是以隻應該用#import 導入。是以Objective-C頭檔案隻應該被Objective-C源檔案或其他的Objective-C頭檔案所導入。這種情況下全部使用#import 是合适的。
标準C和C++頭檔案不包含任何Objective-C元素都可以被一般的C或C++檔案導入。因為标準C和C++裡根本沒有#import ,是以也隻能用#include 導入。在Objective-C代碼中使用#include 一緻的導入這些頭檔案。
本條款有助于跨平台項目的無意錯誤。一位Mac開發者引入一份新C或C++頭檔案時可能會忘記添加#define重加載保護,因為在Mac上用#import 導入檔案不會引發問題,但在别的使用#include 的平台就可能出問題。在所有平台一緻的使用#include 意味着要麼全部成功要麼全部失敗,避免了那種另人沮喪的一些平台上可以運作而另一些不行的情況。
Java代碼

- #import <Cocoa/Cocoa.h>
- #include <CoreFoundation/CoreFoundation.h>
- #import "GTMFoo.h"
- #include "base/basictypes.h"
使用架構根
導入架構根的頭檔案而不是分别導入架構頭檔案
看起來從Cocoa或Foundation這些架構裡導入個别的檔案很不錯,但實際上你直接導入架構根頭檔案效率更高。架構根已經被預編譯故可更快的被加載。還有,記住用#import 指令而不是#include 導入Objective-C的架構。
Java代碼

- #import <Foundation/Foundation.h> // good
- #import <Foundation/NSArray.h> // avoid
- #import <Foundation/NSString.h>
- ...
建構時即設定autorelease
當建立新的臨時對象時,在同一行代碼裡就設定autorelease而不是寫到這個方法的後面幾行去
即使這樣可能會造成一些輕微的延遲,但這樣避免了誰不小心把release 去掉,或在release 之前就return 而造成的記憶體洩露,如下
Java代碼

- // AVOID (unless you have a compelling performance reason)
- MyController* controller = [[MyController alloc] init];
- // ... code here that might return ...
- [controller release];
- // BETTER
- MyController* controller = [[[MyController alloc] init] autorelease];
優先autorelease而非retain
對象指派時盡量采用autorelease 而不是retian 模式。
當把一個新建立的對象賦予一個變量的時候,第一件要做的事情就是先釋放原來變量指向的對象以防止記憶體洩露。這裡也有很多"正确的"方法去做這件事。我們選擇autorelease時因為它更不傾向于出錯。小心在密集的循環裡可能會很快填滿autorelease池,而且它也确實會降低效率,但權衡下來還是可以接受的。
Java代碼

- - (void)setFoo:(GMFoo *)aFoo {
- [foo_ autorelease]; // Won't dealloc if |foo_| == |aFoo|
- foo_ = [aFoo retain];
- }
以聲明時的順序dealloc處理執行個體變量
dealloc 應該用在@interface 聲明時同樣的順序處理執行個體變量,這也有助于評審者鑒别。
代碼評審者檢查或修正dealloc 的實作要確定所有retain 的執行個體變量都獲得了釋放。
為了簡化評審dealloc ,将釋放retain 的執行個體變量代碼保持和@interface 裡聲明的順序一緻。如果dealloc 調用了其他方法去釋放執行個體變量,添加注釋說明那些執行個體變量被這些方法所處理了。
Setters copy NSStrings
在NSString 上調用Setters 方法時,永遠使用copy 方式。(不太懂,原文是Setters taking an NSString, should always copy the string it accepts. )
永遠不要retain 一個字元串,這可以防止調用者在你不知到的情況下修改了字元串。不要以為你可以改變NSString 的值,隻有NSMutableString 才能做到。
Java代碼

- - (void)setFoo:(NSString *)aFoo {
- [foo_ autorelease];
- foo_ = [aFoo copy];
- }
避免抛出異常
不要 @throw Objective-C的異常,不過你還是要做好準備捕獲第三方以及系統調用抛出的異常。
我們的确在編譯時加入了-fobjc-exceptions 指令(主要是為了獲得@synchronized ),但我們并不@throw 。當然在使用第三方庫的時候是允許使用@try,@catch, 以及@finally 的。如果你确實使用了,請務必明确到文檔中哪個方向你想抛出什麼異常。
除非你寫的代碼想要泡在MacOS 10.2或更之前,否則不要使用NS_DURING, NS_HANDLER, NS_ENDHANDLER, NS_VALUERETURN and NS_VOIDRETURN 這些宏。
另外你要小心當寫Objective-C++代碼的時候,如果抛出Objective-C異常,那些棧上的對象不會被清理。示例:
Java代碼

- class exceptiontest {
- public:
- exceptiontest() { NSLog(@"Created"); }
- ~exceptiontest() { NSLog(@"Destroyed"); }
- };
- void foo() {
- exceptiontest a;
- NSException *exception = [NSException exceptionWithName:@"foo"
- reason:@"bar"
- userInfo:nil];
- @throw exception;
- }
- int main(int argc, char *argv[]) {
- GMAutoreleasePool pool;
- @try {
- foo();
- }
- @catch(NSException *ex) {
- NSLog(@"exception raised");
- }
- return 0;
- }
将會有如下輸出:
Java代碼

- 2006-09-28 12:34:29.244 exceptiontest[23661] Created
- 2006-09-28 12:34:29.244 exceptiontest[23661] exception raised
注意這裡的析構函數永遠沒有機會被調用。這是在你想用棧上的智能指針比如shared_ptr,linked_ptr ,還有STL對象的時候不得不關注的一個核心問題。我們不得不痛心地說,如果你一定要在Objective-C++代碼裡抛出異常,那就請一定使用C++的異常。永遠不要重新抛出一個Objective-C的異常,也不允許在異常塊即@try,@catch,@finally 裡生成棧上的 C++對象(比如std::string, std::vector 等).
nil檢查
僅在校驗邏輯流程時做nil檢查。
使用nil檢查不是為了防止程式崩潰,而是校驗邏輯流程。向一個空對象發送一條消息是由Objective-C運作時處理的。方法沒有傳回結果,你也可以安心走下去.然而這裡也有一種,依執行架構不同而傳回尺寸和OS X的版本(這段不懂)(見)Apple's documentation for specifics 。
注意這裡和C/C++的空指針檢查是完全不同的,在那些環境裡,并不處理空指針情況并可能導緻你的應用程式崩潰。不過你仍要自己確定提領的指針不為空。
BOOL類型陷阱
整形的轉換為BOOL 型的時候要小心。不要直接和YES做比較。
BOOL 在Objective-C裡被定義為unsigned char,這意味着它不僅僅隻有YES (1)和NO (0)兩個值。不要直接把整形強制轉換為BOOL 型。常見的錯誤發生在把數組大小,指針的值或者邏輯位運算的結果指派到BOOL型中,而這樣就導緻BOOL 值的僅取決于之前整形值的最後一個位元組,有可能出現整形值不為0但被轉為NO的情況。應此把整形轉為BOOL型的時候請使用ternery操作符,保證傳回YES 或NO 值。
在BOOL,_BOOL 以及bool (見C++ Std 4.7.4, 4.12以及C99 Std 6.3.1.2)之間可以安全的交換值或轉型。但BOOL 和Boolean 之間不可,是以對待Boolean 就像上面講的整形一樣就可以了。在Objective-C函數簽名裡僅使用BOOL 。
對BOOL值使用邏輯運算(&&, ||, ! )都是有效的,傳回值也可以安全的轉為BOOL型而不需要ternery操作符。
Java代碼

- // AVOID
- - (BOOL)isBold {
- return [self fontTraits] & NSFontBoldTrait;
- }
- - (BOOL)isValid {
- return [self stringValue];
- }
- // GOOD
- - (BOOL)isBold {
- return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
- }
- - (BOOL)isValid {
- return [self stringValue] != nil;
- }
- - (BOOL)isEnabled {
- return [self isValid] && [self isBold];
- }
還有,不要把BOOL 型變量直接與YES 比較。這樣不僅對于精通C的人很有難度,而且此條款的第一點也說明了這樣做未必能得到你想要的結果。
Java代碼

- // AVOID
- BOOL great = [foo isGreat];
- if (great == YES)
- // ...be great!
- // GOOD
- BOOL great = [foo isGreat];
- if (great)
- // ...be great!
屬性
屬性總的來說是遵循如下告誡的: 屬性是Objective-C 2.0的特性,是以隻能跑在iPhone以及MacOS X 10.5(leopard)或更高的版本。點表示法通路屬性是不被允許的。
命名
一個有屬性關聯執行個體變量都要在後面加下劃線,而該屬性的名稱就是執行個體變量不加尾部的下劃線的名字。
使用@synthesize 辨別以正确的重命名屬性。
Java代碼

- @interface MyClass : NSObject {
- @private
- NSString *name_;
- }
- @property(copy, nonatomic) NSString *name;
- @end
- @implementation MyClass
- @synthesize name = name_;
- @end
位置
在類接口聲明裡,屬性的聲明必須緊挨着執行個體變量塊聲明之後。類定義裡,屬性的定義必須緊挨着@implementation塊。縮進和@interface或@implementation是一樣的。
Java代碼

- @interface MyClass : NSObject {
- @private
- NSString *name_;
- }
- @property(copy, nonatomic) NSString *name;
- @end
- @implementation MyClass
- @synthesize name = name_;
- - (id)init {
- ...
- }
- @end
對字元串使用拷貝特性
NSString 類型的屬性應該永遠聲明為帶有copy attribute
這點是因為NSString 的setters 永遠使用copy 而不是retain 。
永遠不要對CFType屬性使用synthesize
CFType類型應該永遠遵守@dynamic的實作規則。
因為CFType類型不能有reatin 屬性特性,開發者必須自己維護。(下面不懂,原文In the rare case that you do actually want assignment it is better to make that completely clear by actually implementing the setter and getter and commenting why that is the case. )
列出所有的實作指令
為所有屬性使用實作指令,即使預設是@dynamic
盡管@dynamic是預設的,顯式的列出所有屬性實作指令還是讓類中的每個屬性更加清楚。
Java代碼

- // AVOID
- @interface MyClass : NSObject
- @property(readonly) NSString *name;
- @end
- @implementation MyClass
- .
- .
- .
- - (NSString*)name {
- return @"foo";
- }
- @end
- // GOOD
- @interface MyClass : NSObject
- @property(readonly) NSString *name;
- @end
- @implementation MyClass
- @dynamic name;
- .
- .
- .
- - (NSString*)name {
- return @"foo";
- }
- @end
原子性
屬性使用過猶不及。預設情況,所有的synthesized setters 以及 getters 預設都是原子性的,顯然所有的get,set方法都過度使用了同步方法。是以除非你确實需要原子性,否則都将你的屬性聲明為nonatomic
點表示法
我們禁止使用點表示法是基于如下理由的:
1. 點表示法不過是标準函數調用的文法糖,它的可讀性很有争議。也不過就是換個形式調用函數而已。
2. 它使得提領操作含糊不請.首先看: [foo setBar:1] 很清楚你就明白這是Objective-C對象上的操作。那麼看 foo.bar = 1 就不太清楚到底是對Objective-C的對象還是結構體/公用體/C++類進行操作了。
3. 它使得函數調用看起來就像getters
Java代碼

- NSString *upperCase = @"foo".uppercaseString;
不僅使人困惑,也很難在代碼稽核的時候檢查出來。
4. 它隐藏了函數調用
Java代碼

- bar.value += 10;
這實際上做了兩次函數調用(一次get一次set),而如果你的類很複雜的話,你發現一大堆事都在幕後做完了。
Cocoa模式
委托模式
委托對象不應該是retain
一個實作委托模式的類應該:
1. 執行個體變量命名為delegate_ 以顯示這是個委托
2. 是以,訪存方法名就為delegate 和setDelegate
3. delegate_ 變量不能設為retain
MVC模式
将模型與視圖分離,将控制器從視圖和模型中分離,回調APIs使用@protocol
模型與視圖分離: 不要對模型或資料源的表現形式做任何假設。保持資料與表現層的接口的抽象性。模型不對視圖有任何了解(一個好的實踐就是詢問你自己是否能對資料有多個不同形式的表現方法)
控制器從視圖和模型中分離: 不要把"業務邏輯"放到視圖相關的類;這會導緻代碼可複用性下降。讓控制器的類承管代碼,但不要對表現形式做太多假設。
用@protocol 定義回調APIs: 如果不是所有的方法都必須實作就使用@optional (例外:在Objecitve-C 1.0的時候,@optional 還不可用,是以用類别去定義一個"非标準協定(informal protocol)")