@private
Tip
成員變量應(yīng)該聲明為
@private
@interface MyClass : NSObject {
@private
id myInstanceVariable_;
}
// public accessors, setter takes ownership
- (id)myInstanceVariable;
- (void)setMyInstanceVariable:(id)theVar;
@end
Tip
注釋并且明確指定你的類的構(gòu)造函數(shù)。
對于需要繼承你的類的人來說,明確指定構(gòu)造函數(shù)十分重要。這樣他們就可以只重寫一個構(gòu)造函數(shù)(可能是幾個)來保證他們的子類的構(gòu)造函數(shù)會被調(diào)用。這也有助于將來別人調(diào)試你的類時,理解初始化代碼的工作流程。
Tip
當(dāng)你寫子類的時候,如果需要
init…
方法,記得重載父類的指定構(gòu)造函數(shù)。
如果你沒有重載父類的指定構(gòu)造函數(shù),你的構(gòu)造函數(shù)有時可能不會被調(diào)用,這會導(dǎo)致非常隱秘而且難以解決的 bug。
NSObject
的方法Tip
如果重載了
NSObject
類的方法,強烈建議把它們放在@implementation
內(nèi)的起始處,這也是常見的操作方法。
通常適用(但不局限)于 init...
,copyWithZone:
,以及 dealloc
方法。所有 init...
方法應(yīng)該放在一起,copyWithZone:
緊隨其后,最后才是 dealloc
方法。
Tip
不要在 init 方法中,將成員變量初始化為
0
或者nil
;毫無必要。
剛分配的對象,默認(rèn)值都是 0,除了 isa
指針(譯者注:NSObject
的 isa
指針,用于標(biāo)識對象的類型)。所以不要在初始化器里面寫一堆將成員初始化為 0
或者 nil
的代碼。
+new
Tip
不要調(diào)用
NSObject
類方法new
,也不要在子類中重載它。使用alloc
和init
方法創(chuàng)建并初始化對象。
現(xiàn)代的 Ojbective-C 代碼通過調(diào)用 alloc
和 init
方法來創(chuàng)建并 retain 一個對象。由于類方法 new
很少使用,這使得有關(guān)內(nèi)存分配的代碼審查更困難。
Tip
保持類簡單;避免 “廚房水槽(kitchen-sink)” 式的 API。如果一個函數(shù)壓根沒必要公開,就不要這么做。用私有類別保證公共頭文件整潔。
與 C++ 不同,Objective-C 沒有方法來區(qū)分公共的方法和私有的方法 – 所有的方法都是公共的(譯者注:這取決于 Objective-C 運行時的方法調(diào)用的消息機制)。因此,除非客戶端的代碼期望使用某個方法,不要把這個方法放進公共 API 中。盡可能的避免了你你不希望被調(diào)用的方法卻被調(diào)用到。這包括重載父類的方法。對于內(nèi)部實現(xiàn)所需要的方法,在實現(xiàn)的文件中定義一個類別,而不是把它們放進公有的頭文件中。
// 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
中忘記定義這個方法,編譯器不會抱怨(這是因為你沒有在其它的類別中實現(xiàn)這個私有的方法)。解決文案是將方法放進指定類別的 @implemenation
中。
如果你在使用 Objective-C 2.0,相反你應(yīng)該使用 類擴展 [http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_4_section_5.html] 來聲明你的私有類別,例如:
@interface GMFoo () { ... }
這么做確保如果聲明的方法沒有在 @implementation
中實現(xiàn),會觸發(fā)一個編譯器告警。
再次說明,“私有的” 方法其實不是私有的。你有時可能不小心重載了父類的私有方法,因而制造出很難查找的 Bug。通常,私有的方法應(yīng)該有一個相當(dāng)特殊的名字以防止子類無意地重載它們。
Ojbective-C 的類別可以用來將一個大的 @implementation
拆分成更容易理解的小塊,同時,類別可以為最適合的類添加新的、特定應(yīng)用程序的功能。例如,當(dāng)添加一個 “middle truncation” 方法時,創(chuàng)建一個 NSString
的新類別并把方法放在里面,要比創(chuàng)建任意的一個新類把方法放進里面好得多。
#import
and #include
Tip
#import
Ojbective-C/Objective-C++ 頭文件,#include
C/C++ 頭文件。
基于你所包括的頭文件的編程語言,選擇使用 #import
或是 #include
:
#import
。#include
。頭文件應(yīng)該使用 #define 保護 [http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=The__define_Guard#The__define_Guard]。一些 Ojbective-C 的頭文件缺少 #define
保護,需要使用 #import
的方式包含。由于 Objective-C 的頭文件只會被 Objective-C 的源文件及頭文件包含,廣泛地使用 #import
是可以的。
文件中沒有 Objective-C 代碼的標(biāo)準(zhǔn) C、C++ 頭文件,很可能會被普通的 C、C++ 包含。由于標(biāo)準(zhǔn) C、C++ 里面沒有 #import
的用法,這些文件將被 #include
。在 Objective-C 源文件中使用 #include
包含這些頭文件,意味著這些頭文件永遠會在相同的語義下包含。
這條規(guī)則幫助跨平臺的項目避免低級錯誤。某個 Mac 開發(fā)者寫了一個新的 C 或 C++ 頭文件,如果忘記使用 #define
保護,在 Mac 下使用 #import
這個頭文件不回引起問題,但是在其它平臺下使用 #include
將可能編譯失敗。在所有的平臺上統(tǒng)一使用 #include
,意味著構(gòu)造更可能全都成功或者失敗,防止這些文件只能在某些平臺下能夠工作。
#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#import "GTMFoo.h"
#include "base/basictypes.h"
Tip
#import
根框架而不是單獨的零散文件
當(dāng)你試圖從框架(如 Cocoa 或者 Foundation)中包含若干零散的系統(tǒng)頭文件時,實際上包含頂層根框架的話,編譯器要做的工作更少。根框架通常已經(jīng)經(jīng)過預(yù)編譯,加載更快。另外記得使用 #import
而不是 #include
來包含 Objective-C 的框架。
#import <Foundation/Foundation.h> // good
#import <Foundation/NSArray.h> // avoid
#import <Foundation/NSString.h>
...
autorelease
Tip
當(dāng)創(chuàng)建臨時對象時,在同一行使用
autolease
,而不是在同一個方法的后面語句中使用一個單獨的release
。
盡管運行效率會差一點,但避免了意外刪除 release
或者插入 return
語句而導(dǎo)致內(nèi)存泄露的可能。例如:
// 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
優(yōu)先 retain
其次Tip
給對象賦值時遵守
autorelease``之后 ``retain
的模式。
當(dāng)給一個變量賦值新的對象時,必須先釋放掉舊的對象以避免內(nèi)存泄露。有很多 “正確的” 方法可以處理這種情況。我們則選擇 “autorelease
之后 retain
” 的方法,因為事實證明它不容易出錯。注意大的循環(huán)會填滿 autorelease
池,并且可能效率上會差一點,但權(quán)衡之下我們認(rèn)為是可以接受的。
- (void)setFoo:(GMFoo *)aFoo {
[foo_ autorelease]; // Won't dealloc if |foo_| == |aFoo|
foo_ = [aFoo retain];
}
init
和 dealloc
內(nèi)避免使用訪問器Tip
在
init
和dealloc
方法執(zhí)行的過程中,子類可能會處在一個不一致的狀態(tài),所以這些方法中的代碼應(yīng)避免調(diào)用訪問器。
子類尚未初始化,或在 init
和 dealloc
方法執(zhí)行時已經(jīng)被銷毀,會使訪問器方法很可能不可靠。實際上,應(yīng)在這些方法中直接對 ivals 進行賦值或釋放操作。
正確:
- (id)init {
self = [super init];
if (self) {
bar_ = [[NSMutableString alloc] init]; // good
}
return self;
}
- (void)dealloc {
[bar_ release]; // good
[super dealloc];
}
錯誤:
- (id)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string]; // avoid
}
return self;
}
- (void)dealloc {
self.bar = nil; // avoid
[super dealloc];
}
Tip
dealloc
中實例變量被釋放的順序應(yīng)該與它們在@interface
中聲明的順序一致,這有助于代碼審查。
代碼審查者在評審新的或者修改過的 dealloc
實現(xiàn)時,需要保證每個 retained
的實例變量都得到了釋放。
為了簡化 dealloc
的審查,retained
實例變量被釋放的順序應(yīng)該與他們在 @interface
中聲明的順序一致。如果 dealloc
調(diào)用了其它方法釋放成員變量,添加注釋解釋這些方法釋放了哪些實例變量。
setter
應(yīng)復(fù)制 NSStringsTip
接受
NSString
作為參數(shù)的setter
,應(yīng)該總是copy
傳入的字符串。
永遠不要僅僅 retain
一個字符串。因為調(diào)用者很可能在你不知情的情況下修改了字符串。不要假定別人不會修改,你接受的對象是一個 NSString
對象而不是 NSMutableString
對象。
- (void)setFoo:(NSString *)aFoo {
[foo_ autorelease];
foo_ = [aFoo copy];
}
Tip
不要
@throw
Objective-C 異常,同時也要時刻準(zhǔn)備捕獲從第三方或 OS 代碼中拋出的異常。
我們的確允許 -fobjc-exceptions
編譯開關(guān)(主要因為我們要用到 @synchronized
),但我們不使用 @throw
。為了合理使用第三方的代碼,@try
、@catch
和 @finally
是允許的。如果你確實使用了異常,請明確注釋你期望什么方法拋出異常。
不要使用 NS_DURING
、NS_HANDLER
、NS_ENDHANDLER
、NS_VALUERETURN
和 NS_VOIDRETURN
宏,除非你寫的代碼需要在 Mac OS X 10.2 或之前的操作系統(tǒng)中運行。
注意:如果拋出 Objective-C 異常,Objective-C++ 代碼中基于棧的對象不會被銷毀。比如:
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;
}
會輸出:
注意:這里析構(gòu)函數(shù)從未被調(diào)用。這主要會影響基于棧的 smartptr
,比如 shared_ptr
、linked_ptr
,以及所有你可能用到的 STL 對象。因此我們不得不痛苦的說,如果必須在 Objective-C++ 中使用異常,就只用 C++ 的異常機制。永遠不應(yīng)該重新拋出 Objective-C 異常,也不應(yīng)該在 @try
、@catch
或 @finally
語句塊中使用基于棧的 C++ 對象。
Tip
nil
檢查只用在邏輯流程中。
使用 nil
的檢查來檢查應(yīng)用程序的邏輯流程,而不是避免崩潰。Objective-C 運行時會處理向 nil
對象發(fā)送消息的情況。如果方法沒有返回值,就沒關(guān)系。如果有返回值,可能由于運行時架構(gòu)、返回值類型以及 OS X 版本的不同而不同,參見 Apple’s documentation [http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_2_section_3.html] 。
注意,這和 C/C++ 中檢查指針是否為 ‵‵NULL`` 很不一樣,C/C++ 運行時不做任何檢查,從而導(dǎo)致應(yīng)用程序崩潰。因此你仍然需要保證你不會對一個 C/C++ 的空指針解引用。
Tip
將普通整形轉(zhuǎn)換成
BOOL
時要小心。不要直接將BOOL
值與YES
進行比較。
Ojbective-C 中把 BOOL
定義成無符號字符型,這意味著 BOOL
類型的值遠不止 YES``(1)或 ``NO``(0)。不要直接把整形轉(zhuǎn)換成 ``BOOL
。常見的錯誤包括將數(shù)組的大小、指針值及位運算的結(jié)果直接轉(zhuǎn)換成 BOOL
,取決于整型結(jié)果的最后一個字節(jié),很可能會產(chǎn)生一個 NO
值。當(dāng)轉(zhuǎn)換整形至 BOOL
時,使用三目操作符來返回 YES
或者 NO
。(譯者注:讀者可以試一下任意的 256 的整數(shù)的轉(zhuǎn)換結(jié)果,如 256、512 …)
你可以安全在 BOOL
、_Bool
以及 bool
之間轉(zhuǎn)換(參見 C++ Std 4.7.4, 4.12 以及 C99 Std 6.3.1.2)。你不能安全在 BOOL
以及 Boolean
之間轉(zhuǎn)換,因此請把 Boolean
當(dāng)作一個普通整形,就像之前討論的那樣。但 Objective-C 的方法標(biāo)識符中,只使用 BOOL
。
對 BOOL
使用邏輯運算符(&&
,||
和 !
)是合法的,返回值也可以安全地轉(zhuǎn)換成 BOOL
,不需要使用三目操作符。
錯誤的用法:
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}
正確的用法:
(BOOL)isBold { return ([self fontTraits] & NSFontBoldTrait) ? YES : NO; } - (BOOL)isValid { return [self stringValue] != nil; } - (BOOL)isEnabled { return [self isValid] && [self isBold]; } 同樣,不要直接比較 YES/NO
和 BOOL
變量。不僅僅因為影響可讀性,更重要的是結(jié)果可能與你想的不同。 錯誤的用法: BOOL great = [foo isGreat];
if (great == YES)
// ...be great!
正確的用法:
BOOL great = [foo isGreat];
if (great)
// ...be great!
Tip
屬性(Property)通常允許使用,但需要清楚的了解:屬性(Property)是 Objective-C 2.0 的特性,會限制你的代碼只能跑在 iPhone 和 Mac OS X 10.5 (Leopard) 及更高版本上。點引用只允許訪問聲明過的
@property
。
屬性所關(guān)聯(lián)的實例變量的命名必須遵守以下劃線作為后綴的規(guī)則。屬性的名字應(yīng)該與成員變量去掉下劃線后綴的名字一模一樣。
使用 @synthesize
指示符來正確地重命名屬性。
@interface MyClass : NSObject {
@private
NSString *name_;
}
@property(copy, nonatomic) NSString *name;
@end
@implementation MyClass
@synthesize name = name_;
@end
屬性的聲明必須緊靠著類接口中的實例變量語句塊。屬性的定義必須在 @implementation
的類定義的最上方。他們的縮進與包含他們的 @interface
以及 @implementation
語句一樣。
@interface MyClass : NSObject {
@private
NSString *name_;
}
@property(copy, nonatomic) NSString *name;
@end
@implementation MyClass
@synthesize name = name_;
- (id)init {
...
}
@end
copy
屬性(Attribute)應(yīng)總是用 copy
屬性(attribute)聲明 NSString
屬性(property)。
從邏輯上,確保遵守 NSString
的 setter
必須使用 copy
而不是 retain
的原則。
一定要注意屬性(property)的開銷。缺省情況下,所有 synthesize
的 setter
和 getter
都是原子的。這會給每個 get
或者 set
帶來一定的同步開銷。將屬性(property)聲明為 nonatomic
,除非你需要原子性。
點引用是地道的 Objective-C 2.0 風(fēng)格。它被使用于簡單的屬性 set
、get
操作,但不應(yīng)該用它來調(diào)用對象的其它操作。
正確的做法:
NSString *oldName = myObject.name;
myObject.name = @"Alice";
錯誤的做法:
NSArray *array = [[NSArray arrayWithObject:@"hello"] retain];
NSUInteger numberOfItems = array.count; // not a property
array.release; // not a property
Tip
沒有聲明任何實例變量的接口,應(yīng)省略空花括號。
正確的做法:
@interface MyClass : NSObject// Does a lot of stuff- (void)fooBarBam;@end
錯誤的做法:
@interface MyClass : NSObject {}// Does a lot of stuff- (void)fooBarBam;@end
synthesize
實例變量Tip
只運行在 iOS 下的代碼,優(yōu)先考慮使用自動
synthesize
實例變量。
synthesize
實例變量時,使用 @synthesize var = var_;
防止原本想調(diào)用 self.var = blah;
卻不慎寫成了 var = blah;
。
不要synthesize CFType的屬性 CFType應(yīng)該永遠使用@dynamic實現(xiàn)指示符。 盡管CFType不能使用retain屬性特性,開發(fā)者必須自己處理retain和release。很少有情況你需要僅僅對它進行賦值,因此最好顯示地實現(xiàn)getter和setter,并作出注釋說明。 列出所有的實現(xiàn)指示符 盡管@dynamic是默認(rèn)的,顯示列出它以及其它的實現(xiàn)指示符會提高可讀性,代碼閱讀者可以一眼就知道類的每個屬性是如何實現(xiàn)的。
// Header file
@interface Foo : NSObject
// A guy walks into a bar.
@property(nonatomic, copy) NSString *bar;
@end
// Implementation file
@interface Foo ()
@property(nonatomic, retain) NSArray *baz;
@end
@implementation Foo
@synthesize bar = bar_;
@synthesize baz = baz_;
@end
更多建議: