Google Objective-C 風格指南

扉頁

版本:2.36

原作者 : Mike Pinkerton,Greg Miller,Dave MacLachlan 翻譯 : ewangke,Yang.Y

項目主頁:

譯者的話

ewanke

一直想翻譯這個 style guide ,終于在周末花了7個小時的時間用vim敲出了HTML。很多術語的翻譯很難,平時看的中文技術類書籍有限,對很多術語的中文譯法不是很清楚,難免有不恰當之處,請讀者指出并幫我改進:王軻 "ewangke at gmail.com" 2011.03.27

Yang.Y

對 Objective-C 的了解有限,憑著感覺和 C/C++ 方面的理解:

  • 把指南更新到 2.36 版本
  • 調整了一些術語和句子

背景介紹

Objective-C 是 C 語言的擴展,增加了動態類型和面對對象的特性。它被設計成具有易讀易用的,支持復雜的面向對象設計的編程語言。它是 Mac OS X 以及 iPhone 的主要開發語言。

Cocoa 是 Mac OS X 上主要的應用程序框架之一。它由一組 Objective-C 類組成,為快速開發出功能齊全的 Mac OS X 應用程序提供支持。

蘋果公司已經有一份非常全面的 Objective-C 編碼指南。Google 為 C++ 也寫了一份類似的編碼指南。而這份 Objective-C 指南則是蘋果和 Google 常規建議的最佳結合。因此,在閱讀本指南之前,請確定你已經閱讀過:

所有在 Google 的 C++風格指南中所禁止的事情,如未明確說明,也同樣不能在Objective-C++ 中使用。

本文檔的目的在于為所有的 Mac OS X的代碼提供編碼指南及實踐。許多準則是在實際的項目和小組中經過長期的演化、驗證的。Google 開發的開源項目遵從本指南的要求。

Google 已經發布了遵守本指南開源代碼,它們屬于 Google Toolbox for Mac project 項目(本文以縮寫 GTM 指代)。GTM代碼庫中的代碼通常為了可以在不同項目中復用。

注意,本指南不是 Objective-C 教程。我們假定讀者對 Objective-C非常熟悉。如果你剛剛接觸 Objective-C 或者需要溫習,請閱讀 The Objective-C Programming Language

例子

都說一個例子頂上一千句話,我們就從一個例子開始,來感受一下編碼的風格、留白以及命名等等。

一個頭文件的例子,展示了在 @interface 聲明中如何進行正確的注釋以及留白。

    //  Foo.h
    //  AwesomeProject
    //
    //  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 Foo : NSObject {
     @private
      NSString *bar_;
      NSString *bam_;
    }

    // Returns an autoreleased instance of Foo. See -initWithBar: for details
    // about |bar|.
    + (id)fooWithBar:(NSString *)bar;

    // Designated initializer. |bar| is a thing that represents a thing that
    // does a thing.
    - (id)initWithBar:(NSString *)bar;

    // Gets and sets |bar_|.
    - (NSString *)bar;
    - (void)setBar:(NSString *)bar;

    // Does some work with |blah| and returns YES if the work was completed
    // successfully, and NO otherwise.
    - (BOOL)doWorkWithBlah:(NSString *)blah;

    @end

一個源文件的例子,展示了 @implementation 部分如何進行正確的注釋、留白。同時也包括了基于引用實現的一些重要方法,如 getterssettersinit 以及 dealloc

    //
    //  Foo.m
    //  AwesomeProject
    //
    //  Created by Greg Miller on 6/13/08.
    //  Copyright 2008 Google, Inc. All rights reserved.
    //

    #import "Foo.h"

    @implementation Foo

    + (id)fooWithBar:(NSString *)bar {
      return [[[self alloc] initWithBar:bar] autorelease];
    }

    // Must always override super's designated initializer.
    - (id)init {
      return [self initWithBar:nil];
    }

    - (id)initWithBar:(NSString *)bar {
      if ((self = [super init])) {
        bar_ = [bar copy];
        bam_ = [[NSString alloc] initWithFormat:@"hi %d", 3];
      }
      return self;
    }

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

    - (NSString *)bar {
      return bar_;
    }

    - (void)setBar:(NSString *)bar {
      [bar_ autorelease];
      bar_ = [bar copy];
    }

    - (BOOL)doWorkWithBlah:(NSString *)blah {
      // ...
      return NO;
    }

    @end

不要求在 @interface@implementation@end 前后空行。如果你在 @interface 聲明了實例變量,則須在關括號 } 之后空一行。

除非接口和實現非常短,比如少量的私有方法或橋接類,空行方有助于可讀性。

留白和格式

空格 vs. 制表符

只使用空格,且一次縮進兩個空格。

我們使用空格縮進。不要在代碼中使用制表符。你應該將編輯器設置成自動將制表符替換成空格。

行寬

盡量讓你的代碼保持在 80 列之內。

我們深知 Objective-C 是一門繁冗的語言,在某些情況下略超 80 列可能有助于提高可讀性,但這也只能是特例而已,不能成為開脫。

如果閱讀代碼的人認為把把某行行寬保持在 80 列仍然有不失可讀性,你應該按他們說的去做。

我們意識到這條規則是有爭議的,但很多已經存在的代碼堅持了本規則,我們覺得保證一致性更重要。

通過設置 Xcode > Preferences > Text Editing > Show page guide,來使越界更容易被發現。

方法聲明和定義

  • / + 和返回類型之間須使用一個空格,參數列表中只有參數之間可以有空格。

方法應該像這樣:

    - (void)doSomethingWithString:(NSString *)theString {
      ...
    }

星號前的空格是可選的。當寫新的代碼時,要與先前代碼保持一致。

如果一行有非常多的參數,更好的方式是將每個參數單獨拆成一行。如果使用多行,將每個參數前的冒號對齊。

    - (void)doSomethingWith:(GTMFoo *)theFoo
                       rect:(NSRect)theRect
                   interval:(float)theInterval {
      ...
    }

當第一個關鍵字比其它的短時,保證下一行至少有 4 個空格的縮進。這樣可以使關鍵字垂直對齊,而不是使用冒號對齊:

    - (void)short:(GTMFoo *)theFoo
        longKeyword:(NSRect)theRect
        evenLongerKeyword:(float)theInterval {
      ...
    }

方法調用

方法調用應盡量保持與方法聲明的格式一致。當格式的風格有多種選擇時,新的代碼要與已有代碼保持一致。

調用時所有參數應該在同一行:

    [myObject doFooWith:arg1 name:arg2 error:arg3];

或者每行一個參數,以冒號對齊:

    [myObject doFooWith:arg1
                   name:arg2
                  error:arg3];

不要使用下面的縮進風格:

    [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];

方法定義與方法聲明一樣,當關鍵字的長度不足以以冒號對齊時,下一行都要以四個空格進行縮進。

    [myObj short:arg1
        longKeyword:arg2
        evenLongerKeyword:arg3];

@public@private

@public@private 訪問修飾符應該以一個空格縮進。

與 C++ 中的 public, private 以及 protected 非常相似。

    @interface MyClass : NSObject {
     @public
      ...
     @private
      ...
    }
    @end

異常

每個 @ 標簽應該有獨立的一行,在 @{} 之間需要有一個空格, @catch 與被捕捉到的異常對象的聲明之間也要有一個空格。

如果你決定使用 Objective-C 的異常,那么就按下面的格式。不過你最好先看看 避免拋出異常 了解下為什么不要使用異常。

    @try {
      foo();
    }
    @catch (NSException *ex) {
      bar(ex);
    }
    @finally {
      baz();
    }

協議名

類型標識符和尖括號內的協議名之間,不能有任何空格。 這條規則適用于類聲明、實例變量以及方法聲明。例如:

    @interface MyProtocoledClass : NSObject<NSWindowDelegate> {
     @private
      id<MyFancyDelegate> delegate_;
    }
    - (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
    @end

塊(閉包)

塊(block)適合用在 target/selector 模式下創建回調方法時,因為它使代碼更易讀。塊中的代碼應該縮進 4 個空格。

取決于塊的長度,下列都是合理的風格準則:

  • 如果一行可以寫完塊,則沒必要換行。
  • 如果不得不換行,關括號應與塊聲明的第一個字符對齊。
  • 塊內的代碼須按 4 空格縮進。
  • 如果塊太長,比如超過 20 行,建議把它定義成一個局部變量,然后再使用該變量。
  • 如果塊不帶參數,^{ 之間無須空格。如果帶有參數,^( 之間無須空格,但 ) { 之間須有一個空格。
  • 塊內允許按兩個空格縮進,但前提是和項目的其它代碼保持一致的縮進風格。
    // The entire block fits on one line.
    [operation setCompletionBlock:^{ [self onOperationDone]; }];

    // The block can be put on a new line, indented four spaces, with the
    // closing brace aligned with the first character of the line on which
    // block was declared.
    [operation setCompletionBlock:^{
        [self.delegate newDataAvailable];
    }];

    // Using a block with a C API follows the same alignment and spacing
    // rules as with Objective-C.
    dispatch_async(fileIOQueue_, ^{
        NSString* path = [self sessionFilePath];
        if (path) {
          // ...
        }
    });

    // An example where the parameter wraps and the block declaration fits
    // on the same line. Note the spacing of |^(SessionWindow *window) {|
    // compared to |^{| above.
    [[SessionService sharedService]
        loadWindowWithCompletionBlock:^(SessionWindow *window) {
            if (window) {
              [self windowDidLoad:window];
            } else {
              [self errorLoadingWindow];
            }
        }];

    // An example where the parameter wraps and the block declaration does
    // not fit on the same line as the name.
    [[SessionService sharedService]
        loadWindowWithCompletionBlock:
            ^(SessionWindow *window) {
                if (window) {
                  [self windowDidLoad:window];
                } else {
                  [self errorLoadingWindow];
                }
            }];

    // Large blocks can be declared out-of-line.
    void (^largeBlock)(void) = ^{
        // ...
    };
    [operationQueue_ addOperationWithBlock:largeBlock];

命名

對于易維護的代碼而言,命名規則非常重要。Objective-C 的方法名往往十分長,但代碼塊讀起來就像散文一樣,不需要太多的代碼注釋。

當編寫純粹的 Objective-C 代碼時,我們基本遵守標準的 Objective-C naming rules,這些命名規則可能與 C++ 風格指南中的大相徑庭。例如,Google 的 C++ 風格指南中推薦使用下劃線分隔的單詞作為變量名,而(蘋果的)風格指南則使用駝峰命名法,這在 Objective-C 社區中非常普遍。

任何的類、類別、方法以及變量的名字中都使用全大寫的 首字母縮寫。這遵守了蘋果的標準命名方式,如 URL、TIFF 以及 EXIF。

當編寫 Objective-C++ 代碼時,事情就不這么簡單了。許多項目需要實現跨平臺的 C++ API,并混合一些 Objective-C、Cocoa 代碼,或者直接以 C++ 為后端,前端用本地 Cocoa 代碼。這就導致了兩種命名方式直接不統一。

我們的解決方案是:編碼風格取決于方法/函數以哪種語言實現。如果在一個 @implementation 語句中,就使用 Objective-C 的風格。如果實現一個 C++ 的類,就使用 C++ 的風格。這樣避免了一個函數里面實例變量和局部變量命名規則混亂,嚴重影響可讀性。

文件名

文件名須反映出其實現了什么類 - 包括大小寫。遵循你所參與項目的約定。

文件的擴展名應該如下:

.h : C/C++/Objective-C 的頭文件 .m : Ojbective-C 實現文件 .mm : Ojbective-C++ 的實現文件 .cc : 純 C++ 的實現文件 .c : 純 C 的實現文件

類別的文件名應該包含被擴展的類名,如:GTMNSString+Utils.h 或"GTMNSTextView+Autocomplete.h"。

Objective-C++

源代碼文件內,Ojbective-C++ 代碼遵循你正在實現的函數/方法的風格。

為了最小化 Cocoa/Objective-C 與 C++ 之間命名風格的沖突,根據待實現的函數/方法選擇編碼風格。實現 @implementation 語句塊時,使用 Objective-C 的命名規則;如果實現一個 C++ 的類,就使用 C++ 命名規則。

    // 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 只有一個名字空間。如果代碼不會分享出去,也不會運行在不同的地址空間中,方法名字就不那么重要了。

類名與包含類別名的括號之間,應該以一個空格分隔。

Objective-C 方法名

方法名應該以小寫字母開頭,并混合駝峰格式。每個具名參數也應該以小寫字母開頭。

方法名應盡量讀起來就像句子,這表示你應該選擇與方法名連在一起讀起來通順的參數名。(例如,convertPoint:fromRect:replaceCharactersInRange:withString:)。詳情參見 Apple's Guide to Naming Methods

訪問器方法應該與他們 要獲取的 成員變量的名字一樣,但不應該以get作為前綴。例如:

    - (id)getDelegate;  // AVOID
    - (id)delegate;     // GOOD

這僅限于 Objective-C 的方法名。C++ 的方法與函數的命名規則應該遵從 C++ 風格指南中的規則。

變量名

變量名應該以小寫字母開頭,并使用駝峰格式。類的成員變量應該以下劃線作為后綴。例如:myLocalVariablemyInstanceVariable_。如果不能使用 Objective-C 2.0 的 @property,使用 KVO/KVC 綁定的成員變量可以以一個下劃線作為前綴。

普通變量名

對于靜態的屬性(int 或指針),不要使用匈牙利命名法。盡量為變量起一個描述性的名字。不要擔心浪費列寬,因為讓新的代碼閱讀者立即理解你的代碼更重要。例如:

  • 錯誤的命名:
     int w;
     int nerr;
     int nCompConns;
     tix = [[NSMutableArray alloc] init];
     obj = [someObject object];
     p = [network port];
  • 正確的命名:
     int numErrors;
     int numCompletedConnections;
     tickets = [[NSMutableArray alloc] init];
     userInfo = [someObject object];
     port = [network port];

實例變量

實例變量應該混合大小寫,并以下劃線作為后綴,如 usernameTextField_。然而,如果不能使用 Objective-C 2.0(操作系統版本的限制),并且使用了 KVO/KVC 綁定成員變量時,我們允許例外(譯者注: KVO=Key Value Observing,KVC=Key Value Coding)。這種情況下,可以以一個下劃線作為成員變量名字的前綴,這是蘋果所接受的鍵/值命名慣例。如果可以使用 Objective-C 2.0,@property 以及 @synthesize 提供了遵從這一命名規則的解決方案。

常量

常量名(如宏定義、枚舉、靜態局部變量等)應該以小寫字母 k 開頭,使用駝峰格式分隔單詞,如:kInvalidHandle,kWritePerm

注釋

雖然寫起來很痛苦,但注釋是保證代碼可讀性的關鍵。下面的規則給出了你應該什么時候、在哪進行注釋。記住:盡管注釋很重要,但最好的代碼應該自成文檔。與其給類型及變量起一個晦澀難懂的名字,再為它寫注釋,不如直接起一個有意義的名字。

當你寫注釋的時候,記得你是在給你的聽眾寫,即下一個需要閱讀你所寫代碼的貢獻者。大方一點,下一個讀代碼的人可能就是你!

記住所有 C++ 風格指南里的規則在這里也同樣適用,不同的之處后續會逐步指出。

文件注釋

每個文件的開頭以文件內容的簡要描述起始,緊接著是作者,最后是版權聲明和/或許可證樣板。

版權信息及作者

每個文件應該按順序包括如下項:

  • 文件內容的簡要描述
  • 代碼作者
  • 版權信息聲明(如:Copyright 2008 Google Inc.
  • 必要的話,加上許可證樣板。為項目選擇一個合適的授權樣板(例如,Apache 2.0, BSD, LGPL, GPL)。

如果你對其他人的原始代碼作出重大的修改,請把你自己的名字添加到作者里面。當另外一個代碼貢獻者對文件有問題時,他需要知道怎么聯系你,這十分有用。

聲明部分的注釋

每個接口、類別以及協議應輔以注釋,以描述它的目的及與整個項目的關系。

// A delegate for NSApplication to handle notifications about app
// launch and shutdown. Owned by the main app controller.
@interface MyAppDelegate : NSObject {
  ...
}
@end

如果你已經在文件頭部詳細描述了接口,可以直接說明 "完整的描述請參見文件頭部",但是一定要有這部分注釋。

另外,公共接口的每個方法,都應該有注釋來解釋它的作用、參數、返回值以及其它影響。

為類的線程安全性作注釋,如果有的話。如果類的實例可以被多個線程訪問,記得注釋多線程條件下的使用規則。

實現部分的注釋

使用 | 來引用注釋中的變量名及符號名而不是使用引號。

這會避免二義性,尤其是當符號是一個常用詞匯,這使用語句讀起來很糟糕。例如,對于符號 count

// Sometimes we need |count| to be less than zero.

或者當引用已經包含引號的符號:

// Remember to call |StringWithoutSpaces("foo bar baz")|

對象所有權

當與 Objective-C 最常規的作法不同時,盡量使指針的所有權模型盡量明確。

繼承自 NSObject 的對象的實例變量指針,通常被假定是強引用關系(retained),某些情況下也可以注釋為弱引用(weak)或使用 __weak 生命周期限定符。同樣,聲明的屬性如果沒有被類 retained,必須指定是弱引用或賦予 @property 屬性。然而,Mac 軟件中標記上 IBOutlets 的實例變量,被認為是不會被類 retained 的。

當實例變量指向 CoreFoundation、C++ 或者其它非 Objective-C 對象時,不論指針是否會被 retained,都需要使用 __strong__weak 類型修飾符明確指明。CoreFoundation 和其它非 Objective-C 對象指針需要顯式的內存管理,即便使用了自動引用計數或垃圾回收機制。當不允許使用 __weak 類型修飾符(比如,使用 clang 編譯時的 C++ 成員變量),應使用注釋替代說明。

注意:Objective-C 對象中的 C++ 對象的自動封裝,缺省是不允許的,參見 這里 的說明。

強引用及弱引用聲明的例子:

@interface MyDelegate : NSObject {
 @private
  IBOutlet NSButton *okButton_;  // normal NSControl; implicitly weak on Mac only

  AnObjcObject* doohickey_;  // my doohickey
  __weak MyObjcParent *parent_;  // so we can send msgs back (owns me)

  // non-NSObject pointers...
  __strong CWackyCPPClass *wacky_;  // some cross-platform object
  __strong CFDictionaryRef *dict_;
}
@property(strong, nonatomic) NSString *doohickey;
@property(weak, nonatomic) NSString *parent;
@end

(譯注:強引用 - 對象被類 retained。弱引用 - 對象沒有被類 retained,如委托)

Cocoa 和 Objective-C 特性

成員變量應該是 @private

成員變量應該聲明為 @private

@interface MyClass : NSObject {
 @private
  id myInstanceVariable_;
}
// public accessors, setter takes ownership
- (id)myInstanceVariable;
- (void)setMyInstanceVariable:(id)theVar;
@end

明確指定構造函數

注釋并且明確指定你的類的構造函數。

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

重載指定構造函數

當你寫子類的時候,如果需要 init… 方法,記得重載父類的指定構造函數。

如果你沒有重載父類的指定構造函數,你的構造函數有時可能不會被調用,這會導致非常隱秘而且難以解決的 bug。

重載 NSObject 的方法

如果重載了 NSObject 類的方法,強烈建議把它們放在 @implementation 內的起始處,這也是常見的操作方法。

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

初始化

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

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

避免 +new

不要調用 NSObject 類方法 new,也不要在子類中重載它。使用 allocinit 方法創建并初始化對象。

現代的 Ojbective-C 代碼通過調用 allocinit 方法來創建并 retain 一個對象。由于類方法 new 很少使用,這使得有關內存分配的代碼審查更困難。

保持公共 API 簡單

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

與 C++ 不同,Objective-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

Objective-C 2.0 以前,如果你在私有的 @interface 中聲明了某個方法,但在 @implementation 中忘記定義這個方法,編譯器不會抱怨(這是因為你沒有在其它的類別中實現這個私有的方法)。解決文案是將方法放進指定類別的 @implemenation 中。

如果你在使用 Objective-C 2.0,相反你應該使用 類擴展 來聲明你的私有類別,例如:

    @interface GMFoo () { ... }

這么做確保如果聲明的方法沒有在 @implementation 中實現,會觸發一個編譯器告警。

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

Ojbective-C 的類別可以用來將一個大的 @implementation 拆分成更容易理解的小塊,同時,類別可以為最適合的類添加新的、特定應用程序的功能。例如,當添加一個 "middle truncation" 方法時,創建一個 NSString 的新類別并把方法放在里面,要比創建任意的一個新類把方法放進里面好得多。

#import and #include

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

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

  • 當包含一個使用 Objective-C、Objective-C++ 的頭文件時,使用 #import
  • 當包含一個使用標準 C、C++ 頭文件時,使用 #include。頭文件應該使用 #define 保護

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

文件中沒有 Objective-C 代碼的標準 C、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"

使用根框架

#import 根框架而不是單獨的零散文件

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

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

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

構建時即設定 autorelease

當創建臨時對象時,在同一行使用 autolease,而不是在同一個方法的后面語句中使用一個單獨的 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];

autorelease 優先 retain 其次

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

當給一個變量賦值新的對象時,必須先釋放掉舊的對象以避免內存泄露。有很多 "正確的" 方法可以處理這種情況。我們則選擇 "autorelease 之后 retain" 的方法,因為事實證明它不容易出錯。注意大的循環會填滿 autorelease 池,并且可能效率上會差一點,但權衡之下我們認為是可以接受的。

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

initdealloc 內避免使用訪問器

initdealloc 方法執行的過程中,子類可能會處在一個不一致的狀態,所以這些方法中的代碼應避免調用訪問器。

子類尚未初始化,或在 initdealloc 方法執行時已經被銷毀,會使訪問器方法很可能不可靠。實際上,應在這些方法中直接對 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];
    }

按聲明順序銷毀實例變量

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

代碼審查者在評審新的或者修改過的 dealloc 實現時,需要保證每個 retained 的實例變量都得到了釋放。

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

setter 應復制 NSStrings

接受 NSString 作為參數的 setter,應該總是 copy 傳入的字符串。

永遠不要僅僅 retain 一個字符串。因為調用者很可能在你不知情的情況下修改了字符串。不要假定別人不會修改,你接受的對象是一個 NSString 對象而不是 NSMutableString 對象。

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

避免拋異常

不要 @throw Objective-C 異常,同時也要時刻準備捕獲從第三方或 OS 代碼中拋出的異常。

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

不要使用 NS_DURINGNS_HANDLERNS_ENDHANDLERNS_VALUERETURNNS_VOIDRETURN 宏,除非你寫的代碼需要在 Mac OS X 10.2 或之前的操作系統中運行。

注意:如果拋出 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;
    }

會輸出:

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

nil 檢查

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

使用 nil 的檢查來檢查應用程序的邏輯流程,而不是避免崩潰。Objective-C 運行時會處理向 nil 對象發送消息的情況。如果方法沒有返回值,就沒關系。如果有返回值,可能由于運行時架構、返回值類型以及 OS X 版本的不同而不同,參見 Apple's documentation

注意,這和 C/C++ 中檢查指針是否為 ‵‵NULL" 很不一樣,C/C++ 運行時不做任何檢查,從而導致應用程序崩潰。因此你仍然需要保證你不會對一個 C/C++ 的空指針解引用。

BOOL 若干陷阱

將普通整形轉換成 BOOL 時要小心。不要直接將 BOOL 值與 YES 進行比較。

Ojbective-C 中把 BOOL 定義成無符號字符型,這意味著 BOOL 類型的值遠不止 YES``(1)或 ``NO``(0)。不要直接把整形轉換成 ``BOOL。常見的錯誤包括將數組的大小、指針值及位運算的結果直接轉換成 BOOL ,取決于整型結果的最后一個字節,很可能會產生一個 NO 值。當轉換整形至 BOOL 時,使用三目操作符來返回 YES 或者 NO。(譯者注:讀者可以試一下任意的 256 的整數的轉換結果,如 256、512 …)

你可以安全在 BOOL_Bool 以及 bool 之間轉換(參見 C++ Std 4.7.4, 4.12 以及 C99 Std 6.3.1.2)。你不能安全在 BOOL 以及 Boolean 之間轉換,因此請把 Boolean 當作一個普通整形,就像之前討論的那樣。但 Objective-C 的方法標識符中,只使用 BOOL

BOOL 使用邏輯運算符(&&||!)是合法的,返回值也可以安全地轉換成 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/NOBOOL 變量。不僅僅因為影響可讀性,更重要的是結果可能與你想的不同。

錯誤的用法:

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

正確的用法:

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

屬性(Property)

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

命名

屬性所關聯的實例變量的命名必須遵守以下劃線作為后綴的規則。屬性的名字應該與成員變量去掉下劃線后綴的名字一模一樣。

使用 @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)

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

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

原子性

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

點引用

點引用是地道的 Objective-C 2.0 風格。它被使用于簡單的屬性 setget 操作,但不應該用它來調用對象的其它操作。

正確的做法:

    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

沒有實例變量的接口

沒有聲明任何實例變量的接口,應省略空花括號。

正確的做法:

@interface MyClass : NSObject 

// Does a lot of stuff  
-(void)fooBarBam; 

@end

錯誤的做法:

@interface MyClass : NSObject {
}

// Does a lot of stuff
- (void)fooBarBam; @end

自動 synthesize 實例變量

只運行在 iOS 下的代碼,優先考慮使用自動 synthesize 實例變量。

synthesize 實例變量時,使用 @synthesize var = var_; 防止原本想調用 self.var = blah; 卻不慎寫成了 var = blah;

不要synthesize CFType的屬性 CFType應該永遠使用@dynamic實現指示符。 盡管CFType不能使用retain屬性特性,開發者必須自己處理retain和release。很少有情況你需要僅僅對它進行賦值,因此最好顯示地實現getter和setter,并作出注釋說明。 列出所有的實現指示符 盡管@dynamic是默認的,顯示列出它以及其它的實現指示符會提高可讀性,代碼閱讀者可以一眼就知道類的每個屬性是如何實現的。

    // 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

Cocoa 模式

委托模式

委托對象不應該被 retain

實現委托模式的類應:

  1. 擁有一個名為 delegate_ 的實例變量來引用委托。
  2. 因此,訪問器方法應該命名為 delegatesetDelegate:
  3. delegate_ 對象不應該被 retain

模型/視圖/控制器(MVC)

分離模型與視圖。分離控制器與視圖、模型。回調 API 使用 @protocol

  • 分離模型與視圖:不要假設模型或者數據源的表示方法。保持數據源與表示層之間的接口抽象。視圖不需要了解模型的邏輯(主要的規則是問問你自己,對于數據源的一個實例,有沒有可能有多種不同狀態的表示方法)。
  • 分離控制器與模型、視圖:不要把所有的 "業務邏輯" 放進跟視圖有關的類中。這使代碼非常難以復用。使用控制器類來處理這些代碼,但保證控制器不需要了解太多表示層的邏輯。
  • 使用 @protocol 來定義回調 API,如果不是所有的方法都必須實現,使用 @optional``(特例:使用 Objective-C 1.0 時,``@optional 不可用,可使用類別來定義一個 "非正規的協議")。

所屬標簽

無標簽

25选5玩法中奖