了解Cocoa和Objective-C一些特性
了解Cocoa和Objective-C一些特性是本文要介紹的內容,對于Objective-C的一些特性,初學者應該好好的參考一番,文中介紹的夜很詳細。不多說,我們來看內容。
1、成員變量應該定義為@private
參考代碼:
- @interface MyClass : NSObject {
- @private
- id myInstanceVariable_;
- }
- // public accessors, setter takes ownership
- - (id)myInstanceVariable;
- - (void)setMyInstanceVariable:(id)theVar;
- @end
2、明確指定初始化
注釋并說明指定的初始化。
明確指定初始化對想要子類化你的類的時候時很重要的。那樣,子類化時只需要做一個或多個初始化去保證初值即可。這也有助于在以后調試你的類時明了初始化流程。
3、重寫指定初始化
當重寫一個子類并需要init...方法,注意要重寫父類的指定初始化方法。
如果你沒有正確重寫父類的指定初始化方法,你的初始化方法可能不會被調用,這會導致很多微妙而難以排除的錯誤。
4、重寫NSObject的方法
強烈建議在@implementation之后就立即重寫NSObject 的方法。建議重寫 init...,copyWithZone:和 dealloc 方法。init...相關的方法寫在一起, 接下來是 copyWithZone: ,最后是 dealloc。
5、避免調用new方法
不要調用NSObject 的new方法,也不要在子類中重寫它,而是應該使用 alloc 和 init 方法來初始化retained的對象。
Objective-C代碼顯式調用 alloc 和 init 方法來創建和retain一個對象。new 的方法可能會帶來內存上調試的麻煩。
6、初始化變量
沒必要在初始化方法里把變量初始化為0或者nil,這是多余的。
所有新分配內存的對象內容都初始化為0(除了isa),所以不要在init方法里做無謂的重初始化為0的操作。
7、保持公有API簡明
保持你的類簡單,如果一個方法沒必要公開就不要公開。使用私有類別保證公開頭文件的簡潔。
和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
8、#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意味著要么全部成功要么全部失敗,避免了那種一些平臺上可以運作而另一些不行的情況。
- #import <Cocoa/Cocoa.h>
- #include <CoreFoundation/CoreFoundation.h>
- #import "GTMFoo.h"
- #include "base/basictypes.h"
9、使用根框架
導入框架根的頭文件而不是分別導入框架頭文件
看起來從Cocoa或Foundation這些框架里導入個別的文件很不錯,但實際上你直接導入框架根頭文件效率更高。框架根已經被預編譯故可更快的被加載。還有,記住用#import指令而不是#include導入Objective-C的框架。
- #import <Foundation/Foundation.h> // good
- #import <Foundation/NSArray.h> // avoid
- #import <Foundation/NSString.h>
10、構建時即設定autorelease
當創建新的臨時對象時,在同一行代碼里就設定autorelease而不是寫到這個方法的后面幾行去
即使這樣可能會造成一些輕微的延遲,但這樣避免了誰不小心把release去掉,或在release之前就return而造成的內存泄露,如下:
- // 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];
11、優先autorelease而非retain
對象賦值時盡量采用autorelease而不是retian模式。
當把一個新創建的對象賦予一個變量的時候,第一件要做的事情就是先釋放原來變量指向的對象以防止內存泄露。這里也有很多"正確的"方法去做這件事。我們選擇autorelease時因為它更不傾向于出錯。小心在密集的循環里可能會很快填滿autorelease池,而且它也確實會降低效率,但權衡下來還是可以接受的。
- - (void)setFoo:(GMFoo *)aFoo {
- [foo_ autorelease]; // Won't dealloc if |foo_| == |aFoo|
- foo_ = [aFoo retain];
- }
12、以聲明時的順序dealloc處理實例變量
dealloc應該用在@interface聲明時同樣的順序處理實例變量,這也有助于評審者鑒別。
代碼評審者檢查或修正dealloc的實現要確保所有retain的實例變量都獲得了釋放。
為了簡化評審dealloc,將釋放retain的實例變量代碼保持和@interface里聲明的順序一致。如果dealloc調用了其他方法去釋放實例變量,添加注釋說明那些實例變量被這些方法所處理了。
13、Setters copy NSStrings
在NSString上調用Setters方法時,永遠使用copy方式。永遠不要retain一個字符串,這可以防止調用者在你不知到的情況下修改了字符串。不要以為你可以改變NSString的值,只有NSMutableString才能做到。
- - (void)setFoo:(NSString *)aFoo {
- [foo_ autorelease];
- foo_ = [aFoo copy];
- }
#p#
14、避免拋出異常
不要@throwObjective-C的異常,不過你還是要做好準備捕獲第三方以及系統調用拋出的異常。
我們的確在編譯時加入了-fobjc-exceptions指令(主要是為了獲得@synchronized),但我們并不@throw。當然在使用第三方庫的時候是允許使用@try,@catch,以及@finally的。如果你確實使用了,請務必明確到文檔中哪個方向你想拋出什么異常。
除非你寫的代碼想要泡在MacOS10.2或更之前,否則不要使用NS_DURING,NS_HANDLER,NS_ENDHANDLER,NS_VALUERETURNandNS_VOIDRETURN這些宏。
另外你要小心當寫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;
- }
將會有如下輸出:
- 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等)。
15、nil檢查
僅在校驗邏輯流程時做nil檢查。
使用nil檢查不是為了防止程序崩潰,而是校驗邏輯流程。向一個空對象發送一條消息是由Objective-C運行時處理的。方法沒有返回結果,你也可以安心走下去。
注意這里和C/C++的空指針檢查是完全不同的,在那些環境里,并不處理空指針情況并可能導致你的應用程序崩潰。不過你仍要自己確保提領的指針不為空。
16、 BOOL類型陷阱
整形的轉換為BOOL型的時候要小心。不要直接和YES做比較。
BOOL在Objective-C里被定義為unsignedchar,這意味著它不僅僅只有YES(1)和NO(0)兩個值。不要直接把整形強制轉換為BOOL型。常見的錯誤發生在把數組大小,指針的值或者邏輯位運算的結果賦值到BOOL型中,而這樣就導致BOOL值的僅取決于之前整形值的最后一個字節,有可能出現整形值不為0但被轉為NO的情況。應此把整形轉為BOOL型的時候請使用ternery操作符,保證返回YES或NO值。
在BOOL,_BOOL以及bool(見C++Std4.7.4,4.12以及C99Std6.3.1.2)之間可以安全的交換值或轉型。但BOOL和Boolean之間不可,所以對待Boolean就像上面講的整形一樣就可以了。在Objective-C函數簽名里僅使用BOOL。
對BOOL值使用邏輯運算(&&,||,!)都是有效的,返回值也可以安全的轉為BOOL型而不需要ternery操作符。
- - (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];
- }
還有,不要把BOOL型變量直接與YES比較。這樣不僅對于精通C的人很有難度,而且此條款的第一點也說明了這樣做未必能得到你想要的結果。
- BOOL great = [foo isGreat];
- if (great == YES)
- // ...be great!
- BOOL great = [foo isGreat];
- if (great)
- // ...be great!
17、屬性
屬性遵循如下規則:屬性是Objective-C2.0的特性,所以只能跑在iPhone以及MacOSX10.5(leopard)或更高的版本。
一個有屬性關聯實例變量都要在后面加下劃線,而該屬性的名稱就是實例變量不加尾部的下劃線的名字。
使用@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
18、為NSString使用Copy屬性
NSString的屬性定義為copy。
如果自己實現setters方法,也請使用copy而不是retain。
小結:了解Cocoa和Objective-C一些特性的內容介紹完了,希望本文對你有所幫助!