Cocoa 和 Objective-C 特性

2018-08-12 21:19 更新

Cocoa 和 Objective-C 特性

成員變量應(yīng)該是 @private

Tip
    成員變量應(yīng)該聲明為 ``@private``
    @interface MyClass : NSObject {
     @private
      id myInstanceVariable_;
    }
    // public accessors, setter takes ownership
    - (id)myInstanceVariable;
    - (void)setMyInstanceVariable:(id)theVar;
    @end

明確指定構(gòu)造函數(shù)

Tip
    注釋并且明確指定你的類的構(gòu)造函數(shù)。

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

重載指定構(gòu)造函數(shù)

Tip
    當(dāng)你寫子類的時(shí)候,如果需要 ``init…`` 方法,記得重載父類的指定構(gòu)造函數(shù)。

如果你沒有重載父類的指定構(gòu)造函數(shù),你的構(gòu)造函數(shù)有時(shí)可能不會(huì)被調(diào)用,這會(huì)導(dǎo)致非常隱秘而且難以解決的 bug。

重載 NSObject 的方法

Tip
    如果重載了 ``NSObject`` 類的方法,強(qiáng)烈建議把它們放在 ``@implementation`` 內(nèi)的起始處,這也是常見的操作方法。

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

初始化

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

剛分配的對象,默認(rèn)值都是 0,除了 isa 指針(譯者注:NSObjectisa 指針,用于標(biāo)識(shí)對象的類型)。所以不要在初始化器里面寫一堆將成員初始化為 0 或者 nil 的代碼。

避免 +new

Tip
    不要調(diào)用 ``NSObject`` 類方法 ``new``,也不要在子類中重載它。使用 ``alloc`` 和 ``init`` 方法創(chuàng)建并初始化對象。

現(xiàn)代的 Ojbective-C 代碼通過調(diào)用 allocinit 方法來創(chuàng)建并 retain 一個(gè)對象。由于類方法 new 很少使用,這使得有關(guān)內(nèi)存分配的代碼審查更困難。

保持公共 API 簡單

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

與 C++ 不同,Objective-C 沒有方法來區(qū)分公共的方法和私有的方法 -- 所有的方法都是公共的(譯者注:這取決于 Objective-C 運(yùn)行時(shí)的方法調(diào)用的消息機(jī)制)。因此,除非客戶端的代碼期望使用某個(gè)方法,不要把這個(gè)方法放進(jìn)公共 API 中。盡可能的避免了你你不希望被調(diào)用的方法卻被調(diào)用到。這包括重載父類的方法。對于內(nèi)部實(shí)現(xiàn)所需要的方法,在實(shí)現(xiàn)的文件中定義一個(gè)類別,而不是把它們放進(jì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 中聲明了某個(gè)方法,但在 @implementation 中忘記定義這個(gè)方法,編譯器不會(huì)抱怨(這是因?yàn)槟銢]有在其它的類別中實(shí)現(xiàn)這個(gè)私有的方法)。解決文案是將方法放進(jìn)指定類別的 @implemenation 中。

如果你在使用 Objective-C 2.0,相反你應(yīng)該使用 類擴(kuò)展 <http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_4_section_5.html>_ 來聲明你的私有類別,例如:

    @interface GMFoo () { ... }

這么做確保如果聲明的方法沒有在 @implementation 中實(shí)現(xiàn),會(huì)觸發(fā)一個(gè)編譯器告警。

再次說明,“私有的” 方法其實(shí)不是私有的。你有時(shí)可能不小心重載了父類的私有方法,因而制造出很難查找的 Bug。通常,私有的方法應(yīng)該有一個(gè)相當(dāng)特殊的名字以防止子類無意地重載它們。

Ojbective-C 的類別可以用來將一個(gè)大的 @implementation 拆分成更容易理解的小塊,同時(shí),類別可以為最適合的類添加新的、特定應(yīng)用程序的功能。例如,當(dāng)添加一個(gè) “middle truncation” 方法時(shí),創(chuàng)建一個(gè) NSString 的新類別并把方法放在里面,要比創(chuàng)建任意的一個(gè)新類把方法放進(jìn)里面好得多。

#import and #include

    ``#import`` Ojbective-C/Objective-C++ 頭文件,``#include`` C/C++ 頭文件。

基于你所包括的頭文件的編程語言,選擇使用 #import 或是 #include

  • 當(dāng)包含一個(gè)使用 Objective-C、Objective-C++ 的頭文件時(shí),使用 #import 。
  • 當(dāng)包含一個(gè)使用標(biāo)準(zhǔn) C、C++ 頭文件時(shí),使用 #include。頭文件應(yīng)該使用 #define 保護(hù) <http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=The__define_Guard#The__define_Guard>_。

一些 Ojbective-C 的頭文件缺少 #define 保護(hù),需要使用 #import 的方式包含。由于 Objective-C 的頭文件只會(huì)被 Objective-C 的源文件及頭文件包含,廣泛地使用 #import 是可以的。

文件中沒有 Objective-C 代碼的標(biāo)準(zhǔn) C、C++ 頭文件,很可能會(huì)被普通的 C、C++ 包含。由于標(biāo)準(zhǔn) C、C++ 里面沒有 #import 的用法,這些文件將被 #include。在 Objective-C 源文件中使用 #include 包含這些頭文件,意味著這些頭文件永遠(yuǎn)會(huì)在相同的語義下包含。

這條規(guī)則幫助跨平臺(tái)的項(xiàng)目避免低級(jí)錯(cuò)誤。某個(gè) Mac 開發(fā)者寫了一個(gè)新的 C 或 C++ 頭文件,如果忘記使用 #define 保護(hù),在 Mac 下使用 #import 這個(gè)頭文件不回引起問題,但是在其它平臺(tái)下使用 #include 將可能編譯失敗。在所有的平臺(tái)上統(tǒng)一使用 #include,意味著構(gòu)造更可能全都成功或者失敗,防止這些文件只能在某些平臺(tái)下能夠工作。

    #import <Cocoa/Cocoa.h>
    #include <CoreFoundation/CoreFoundation.h>
    #import "GTMFoo.h"
    #include "base/basictypes.h"

使用根框架

Tip
    ``#import`` 根框架而不是單獨(dú)的零散文件

當(dāng)你試圖從框架(如 Cocoa 或者 Foundation)中包含若干零散的系統(tǒng)頭文件時(shí),實(shí)際上包含頂層根框架的話,編譯器要做的工作更少。根框架通常已經(jīng)經(jīng)過預(yù)編譯,加載更快。另外記得使用 #import 而不是 #include 來包含 Objective-C 的框架。

    #import <Foundation/Foundation.h>     // good

    #import <Foundation/NSArray.h>        // avoid
    #import <Foundation/NSString.h>
    ...

構(gòu)建時(shí)即設(shè)定 autorelease

Tip
    當(dāng)創(chuàng)建臨時(shí)對象時(shí),在同一行使用 ``autolease``,而不是在同一個(gè)方法的后面語句中使用一個(gè)單獨(dú)的 ``release``。

盡管運(yùn)行效率會(huì)差一點(diǎn),但避免了意外刪除 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 其次

    給對象賦值時(shí)遵守 ``autorelease``之后 ``retain`` 的模式。

當(dāng)給一個(gè)變量賦值新的對象時(shí),必須先釋放掉舊的對象以避免內(nèi)存泄露。有很多 “正確的” 方法可以處理這種情況。我們則選擇 “autorelease 之后 retain” 的方法,因?yàn)槭聦?shí)證明它不容易出錯(cuò)。注意大的循環(huán)會(huì)填滿 autorelease 池,并且可能效率上會(huì)差一點(diǎn),但權(quán)衡之下我們認(rèn)為是可以接受的。

    - (void)setFoo:(GMFoo *)aFoo {
      [foo_ autorelease];  // Won't dealloc if |foo_| == |aFoo|
      foo_ = [aFoo retain];
    }

initdealloc 內(nèi)避免使用訪問器

  Tip
    在 ``init`` 和 ``dealloc`` 方法執(zhí)行的過程中,子類可能會(huì)處在一個(gè)不一致的狀態(tài),所以這些方法中的代碼應(yīng)避免調(diào)用訪問器。

子類尚未初始化,或在 initdealloc 方法執(zhí)行時(shí)已經(jīng)被銷毀,會(huì)使訪問器方法很可能不可靠。實(shí)際上,應(yīng)在這些方法中直接對 ivals 進(jìn)行賦值或釋放操作。

正確:

    - (id)init {
      self = [super init];
      if (self) {
        bar_ = [[NSMutableString alloc] init];  // good
      }
      return self;
    }

    - (void)dealloc {
      [bar_ release];                           // good
      [super dealloc];
    }

錯(cuò)誤:

    - (id)init {
      self = [super init];
      if (self) {
        self.bar = [NSMutableString string];  // avoid
      }
      return self;
    }

    - (void)dealloc {
      self.bar = nil;                         // avoid
      [super dealloc];
    }

按聲明順序銷毀實(shí)例變量

    ``dealloc`` 中實(shí)例變量被釋放的順序應(yīng)該與它們在 ``@interface`` 中聲明的順序一致,這有助于代碼審查。

代碼審查者在評審新的或者修改過的 dealloc 實(shí)現(xiàn)時(shí),需要保證每個(gè) retained 的實(shí)例變量都得到了釋放。

為了簡化 dealloc 的審查,retained 實(shí)例變量被釋放的順序應(yīng)該與他們在 @interface 中聲明的順序一致。如果 dealloc 調(diào)用了其它方法釋放成員變量,添加注釋解釋這些方法釋放了哪些實(shí)例變量。

setter 應(yīng)復(fù)制 NSStrings

    接受 ``NSString`` 作為參數(shù)的 ``setter``,應(yīng)該總是 ``copy`` 傳入的字符串。

永遠(yuǎn)不要僅僅 retain 一個(gè)字符串。因?yàn)檎{(diào)用者很可能在你不知情的情況下修改了字符串。不要假定別人不會(huì)修改,你接受的對象是一個(gè) NSString 對象而不是 NSMutableString 對象。

    - (void)setFoo:(NSString *)aFoo {
      [foo_ autorelease];
      foo_ = [aFoo copy];
    }

避免拋異常

Tip
    不要 ``@throw`` Objective-C 異常,同時(shí)也要時(shí)刻準(zhǔn)備捕獲從第三方或 OS 代碼中拋出的異常。

我們的確允許 -fobjc-exceptions 編譯開關(guān)(主要因?yàn)槲覀円玫?@synchronized ),但我們不使用 @throw。為了合理使用第三方的代碼,@try@catch@finally 是允許的。如果你確實(shí)使用了異常,請明確注釋你期望什么方法拋出異常。

不要使用 NS_DURING、NS_HANDLER、NS_ENDHANDLERNS_VALUERETURNNS_VOIDRETURN 宏,除非你寫的代碼需要在 Mac OS X 10.2 或之前的操作系統(tǒng)中運(yùn)行。

注意:如果拋出 Objective-C 異常,Objective-C++ 代碼中基于棧的對象不會(huì)被銷毀。比如:

    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;
    }

會(huì)輸出:

注意:這里析構(gòu)函數(shù)從未被調(diào)用。這主要會(huì)影響基于棧的 smartptr,比如 shared_ptrlinked_ptr,以及所有你可能用到的 STL 對象。因此我們不得不痛苦的說,如果必須在 Objective-C++ 中使用異常,就只用 C++ 的異常機(jī)制。永遠(yuǎn)不應(yīng)該重新拋出 Objective-C 異常,也不應(yīng)該在 @try@catch@finally 語句塊中使用基于棧的 C++ 對象。

nil 檢查

    ``nil`` 檢查只用在邏輯流程中。

使用 nil 的檢查來檢查應(yīng)用程序的邏輯流程,而不是避免崩潰。Objective-C 運(yùn)行時(shí)會(huì)處理向 nil 對象發(fā)送消息的情況。如果方法沒有返回值,就沒關(guān)系。如果有返回值,可能由于運(yùn)行時(shí)架構(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++ 運(yùn)行時(shí)不做任何檢查,從而導(dǎo)致應(yīng)用程序崩潰。因此你仍然需要保證你不會(huì)對一個(gè) C/C++ 的空指針解引用。

BOOL 若干陷阱

    將普通整形轉(zhuǎn)換成 ``BOOL`` 時(shí)要小心。不要直接將 ``BOOL`` 值與 ``YES`` 進(jìn)行比較。

Ojbective-C 中把 BOOL 定義成無符號(hào)字符型,這意味著 BOOL 類型的值遠(yuǎn)不止 YES(1)或 NO(0)。不要直接把整形轉(zhuǎn)換成 BOOL。常見的錯(cuò)誤包括將數(shù)組的大小、指針值及位運(yùn)算的結(jié)果直接轉(zhuǎn)換成 BOOL ,取決于整型結(jié)果的最后一個(gè)字節(jié),很可能會(huì)產(chǎn)生一個(gè) NO 值。當(dāng)轉(zhuǎn)換整形至 BOOL 時(shí),使用三目操作符來返回 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)作一個(gè)普通整形,就像之前討論的那樣。但 Objective-C 的方法標(biāo)識(shí)符中,只使用 BOOL

BOOL 使用邏輯運(yùn)算符(&&,||!)是合法的,返回值也可以安全地轉(zhuǎn)換成 BOOL,不需要使用三目操作符。

錯(cuò)誤的用法:

    - (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/NOBOOL 變量。不僅僅因?yàn)橛绊懣勺x性,更重要的是結(jié)果可能與你想的不同。

錯(cuò)誤的用法:

    BOOL great = [foo isGreat];
    if (great == YES)
      // ...be great!

正確的用法:

    BOOL great = [foo isGreat];
    if (great)
      // ...be great!

屬性(Property)

    屬性(Property)通常允許使用,但需要清楚的了解:屬性(Property)是 Objective-C 2.0 的特性,會(huì)限制你的代碼只能跑在 iPhone 和 Mac OS X 10.5 (Leopard) 及更高版本上。點(diǎn)引用只允許訪問聲明過的 ``@property``。

命名

屬性所關(guān)聯(lián)的實(shí)例變量的命名必須遵守以下劃線作為后綴的規(guī)則。屬性的名字應(yīng)該與成員變量去掉下劃線后綴的名字一模一樣。

使用 @synthesize 指示符來正確地重命名屬性。

    @interface MyClass : NSObject {
     @private
      NSString *name_;
    }
    @property(copy, nonatomic) NSString *name;
    @end

    @implementation MyClass
    @synthesize name = name_;
    @end

位置

屬性的聲明必須緊靠著類接口中的實(shí)例變量語句塊。屬性的定義必須在 @implementation 的類定義的最上方。他們的縮進(jìn)與包含他們的 @interface 以及 @implementation 語句一樣。

    @interface MyClass : NSObject {
     @private
      NSString *name_;
    }
    @property(copy, nonatomic) NSString *name;
    @end

    @implementation MyClass
    @synthesize name = name_;
    - (id)init {
    ...
    }
    @end

字符串應(yīng)使用 copy 屬性(Attribute)

應(yīng)總是用 copy 屬性(attribute)聲明 NSString 屬性(property)。

從邏輯上,確保遵守 NSStringsetter 必須使用 copy 而不是 retain 的原則。

原子性

一定要注意屬性(property)的開銷。缺省情況下,所有 synthesizesettergetter 都是原子的。這會(huì)給每個(gè) get 或者 set 帶來一定的同步開銷。將屬性(property)聲明為 nonatomic,除非你需要原子性。

點(diǎn)引用

點(diǎn)引用是地道的 Objective-C 2.0 風(fēng)格。它被使用于簡單的屬性 set、get 操作,但不應(yīng)該用它來調(diào)用對象的其它操作。

正確的做法:

    NSString *oldName = myObject.name;
    myObject.name = @"Alice";

錯(cuò)誤的做法:

    NSArray *array = [[NSArray arrayWithObject:@"hello"] retain];

    NSUInteger numberOfItems = array.count;  // not a property
    array.release;                           // not a property

沒有實(shí)例變量的接口

    沒有聲明任何實(shí)例變量的接口,應(yīng)省略空花括號(hào)。

正確的做法:

    @interface MyClass : NSObject
    // Does a lot of stuff
    - (void)fooBarBam;
    @end

錯(cuò)誤的做法:

    @interface MyClass : NSObject {
    }
    // Does a lot of stuff
    - (void)fooBarBam;
    @end

自動(dòng) synthesize 實(shí)例變量

Tip
    只運(yùn)行在 iOS 下的代碼,優(yōu)先考慮使用自動(dòng) ``synthesize`` 實(shí)例變量。

synthesize 實(shí)例變量時(shí),使用 @synthesize var = var_; 防止原本想調(diào)用 self.var = blah; 卻不慎寫成了 var = blah;。

不要synthesize CFType的屬性 CFType應(yīng)該永遠(yuǎn)使用@dynamic實(shí)現(xiàn)指示符。 盡管CFType不能使用retain屬性特性,開發(fā)者必須自己處理retain和release。很少有情況你需要僅僅對它進(jìn)行賦值,因此最好顯示地實(shí)現(xiàn)getter和setter,并作出注釋說明。 列出所有的實(shí)現(xiàn)指示符 盡管@dynamic是默認(rèn)的,顯示列出它以及其它的實(shí)現(xiàn)指示符會(huì)提高可讀性,代碼閱讀者可以一眼就知道類的每個(gè)屬性是如何實(shí)現(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
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)