鍍金池/ 教程/ iOS/ 類(lèi)
Case語(yǔ)句
類(lèi)
美化代碼
參考資料
對(duì)象間的通訊
命名
條件語(yǔ)句
Protocols
NSNotification
面向切面編程
Categories
代碼組織

類(lèi)

類(lèi)名

類(lèi)名應(yīng)加上 個(gè)大寫(xiě)字母作為前綴(兩個(gè)字母的為 Apple 的類(lèi)保留)。雖然這個(gè)規(guī)范看起來(lái)難看,但是這樣做是為了減少 objective-c 沒(méi)有命名空間所帶來(lái)的問(wèn)題。

一些開(kāi)發(fā)者在定義 Model 對(duì)象時(shí)并不遵循這個(gè)規(guī)范(對(duì)于 Core Data 對(duì)象,我們更應(yīng)該遵循這個(gè)規(guī)范)。我們建議在定義 Core Data 對(duì)象時(shí)嚴(yán)格遵循這個(gè)約定,因?yàn)槟阕詈罂赡馨涯愕?Managed Object Model 和其他(第三方庫(kù))的 Managed Object Model 合并。

你可能注意到了,這本書(shū)里的類(lèi)的前綴(其實(shí)不僅僅是類(lèi))是ZOC

另一個(gè)類(lèi)的命名規(guī)范:當(dāng)你創(chuàng)建一個(gè)子類(lèi)的時(shí)候,你應(yīng)該把說(shuō)明性的部分放在前綴和父類(lèi)名的在中間。舉個(gè)例子:如果你有一個(gè) ZOCNetworkClient 類(lèi),子類(lèi)的名字會(huì)是ZOCTwitterNetworkClient (注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之間); 按照這個(gè)約定, 一個(gè)UIViewController 的子類(lèi)會(huì)是 ZOCTimelineViewController.

Initializer 和 dealloc 初始化

推薦的代碼組織方式:將 dealloc 方法放在實(shí)現(xiàn)文件的最前面(直接在 @synthesize 以及 @dynamic 之后),init 應(yīng)該放在 dealloc 之后。如果有多個(gè)初始化方法, designated initializer 應(yīng)該放在第一個(gè),secondary initializer 在之后緊隨,這樣邏輯性更好。 如今有了 ARC,dealloc 方法幾乎不需要實(shí)現(xiàn),不過(guò)把 init 和 dealloc 放在一起可以從視覺(jué)上強(qiáng)調(diào)它們是一對(duì)的。通常,在 init 方法中做的事情需要在 dealloc 方法中撤銷(xiāo)。

init 方法應(yīng)該是這樣的結(jié)構(gòu):

- (instancetype)init
{
    self = [super init]; // call the designated initializer
    if (self) {
        // Custom initialization
    }
    return self;
}

為什么設(shè)置 self[super init] 的返回值,以及中間發(fā)生了什么呢?這是一個(gè)十分有趣的話題。

讓我們后退一步:我們?cè)?jīng)寫(xiě)了類(lèi)似 [[NSObject alloc] init] 的表達(dá)式, allocinit 區(qū)別慢慢褪去 。一個(gè) Objective-C 的特性叫 兩步創(chuàng)建 。 這意味著申請(qǐng)分配內(nèi)存和初始化是兩個(gè)分離的操作。

  • alloc表示對(duì)象分配內(nèi)存,這個(gè)過(guò)程涉及分配足夠的可用內(nèi)存來(lái)保存對(duì)象,寫(xiě)入isa指針,初始化 retain 的計(jì)數(shù),并且初始化所有實(shí)例變量。
  • init 是表示初始化對(duì)象,這意味著把對(duì)象放到了一個(gè)可用的狀態(tài)。這通常是指把對(duì)象的實(shí)例變量賦給了可用的值。

alloc 方法會(huì)返回一個(gè)合法的沒(méi)有初始化的實(shí)例對(duì)象。每一個(gè)發(fā)送到實(shí)例的信息會(huì)被翻譯為名字是 selfalloc 返回的指針的參數(shù)返回的 objc_msgSend() 的調(diào)用。這樣之后 self 已經(jīng)可以執(zhí)行所有方法了。 為了完成兩步創(chuàng)建,第一個(gè)發(fā)送給新創(chuàng)建的實(shí)例的方法應(yīng)該是約定俗成的 init 方法。注意 NSObjectinit 實(shí)現(xiàn)中,僅僅是返回了 self。

關(guān)于 init 有一個(gè)另外的重要的約定:這個(gè)方法可以(并且應(yīng)該)在不能成功完成初始化的時(shí)候返回 nil;初始化可能因?yàn)楦鞣N原因失敗,比如一個(gè)輸入的格式錯(cuò)誤,或者未能成功初始化一個(gè)需要的對(duì)象。 這樣我們就理解了為什么需要總是調(diào)用 self = [super init]。如果你的超類(lèi)沒(méi)有成功初始化它自己,你必須假設(shè)你在一個(gè)矛盾的狀態(tài),并且在你的實(shí)現(xiàn)中不要處理你自己的初始化邏輯,同時(shí)返回 nil。如果你不是這樣做,你看你會(huì)得到一個(gè)不能用的對(duì)象,并且它的行為是不可預(yù)測(cè)的,最終可能會(huì)導(dǎo)致你的 app 發(fā)生 crash。

重新給 self 賦值同樣可以被 init 利用為在被調(diào)用的時(shí)候返回不同的實(shí)例。一個(gè)例子是 類(lèi)簇 或者其他的返回相同的(不可變的)實(shí)例對(duì)象的 Cocoa 類(lèi)。

Designated 和 Secondary Initializers

Objective-C 有 designated 和 secondary 初始化方法的觀念。 designated 初始化方法是提供所有的參數(shù),secondary 初始化方法是一個(gè)或多個(gè),并且提供一個(gè)或者更多的默認(rèn)參數(shù)來(lái)調(diào)用 designated 初始化方法的初始化方法。

@implementation ZOCEvent

- (instancetype)initWithTitle:(NSString *)title
                         date:(NSDate *)date
                     location:(CLLocation *)location
{
    self = [super init];
    if (self) {
        _title    = title;
        _date     = date;
        _location = location;
    }
    return self;
}

- (instancetype)initWithTitle:(NSString *)title
                         date:(NSDate *)date
{
    return [self initWithTitle:title date:date location:nil];
}

- (instancetype)initWithTitle:(NSString *)title
{
    return [self initWithTitle:title date:[NSDate date] location:nil];
}

@end

initWithTitle:date:location: 就是 designated 初始化方法,另外的兩個(gè)是 secondary 初始化方法。因?yàn)樗鼈儍H僅是調(diào)用類(lèi)實(shí)現(xiàn)的 designated 初始化方法

Designated Initializer

一個(gè)類(lèi)應(yīng)該又且只有一個(gè) designated 初始化方法,其他的初始化方法應(yīng)該調(diào)用這個(gè) designated 的初始化方法(雖然這個(gè)情況有一個(gè)例外)

這個(gè)分歧沒(méi)有要求那個(gè)初始化函數(shù)需要被調(diào)用。

在類(lèi)繼承中調(diào)用任何 designated 初始化方法都是合法的,而且應(yīng)該保證 所有的 designated initializer 在類(lèi)繼承中是是從祖先(通常是 NSObject)到你的類(lèi)向下調(diào)用的。 實(shí)際上這意味著第一個(gè)執(zhí)行的初始化代碼是最遠(yuǎn)的祖先,然后從頂向下的類(lèi)繼承,所有類(lèi)都有機(jī)會(huì)執(zhí)行他們特定的初始化代碼。這樣,你在你做你的特定的初始化工作前,所有你從超類(lèi)繼承的東西是不可用的狀態(tài)。即使它的狀態(tài)不明確,所有 Apple 的框架的 Framework 是保證遵守這個(gè)約定的,而且你的類(lèi)也應(yīng)該這樣做。

當(dāng)定義一個(gè)新類(lèi)的時(shí)候有三個(gè)不同的方式:

  1. 不需要重載任何初始化函數(shù)
  2. 重載 designated initializer
  3. 定義一個(gè)新的 designated initializer

第一個(gè)方案是最簡(jiǎn)單的:你不需要增加類(lèi)的任何初始化邏輯,只需要依照父類(lèi)的designated initializer。 當(dāng)你希望提供額外的初始化邏輯的時(shí)候你可以重載 designated initializer。你只需要重載你的直接的超類(lèi)的 designated initializer 并且確認(rèn)你的實(shí)現(xiàn)調(diào)用了超類(lèi)的方法。 你一個(gè)典型的例子是你創(chuàng)造UIViewController子類(lèi)的時(shí)候重載 initWithNibName:bundle:方法。

@implementation ZOCViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    // call to the superclass designated initializer
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

@end

UIViewController 子類(lèi)的例子里面如果重載 init 會(huì)是一個(gè)錯(cuò)誤,這個(gè)情況下調(diào)用者會(huì)嘗試調(diào)用 initWithNib:bundle 初始化你的類(lèi),你的類(lèi)實(shí)現(xiàn)不會(huì)被調(diào)用。著同樣違背了它應(yīng)該是合法調(diào)用任何 designated initializer 的規(guī)則。

在你希望提供你自己的初始化函數(shù)的時(shí)候,你應(yīng)該遵守這三個(gè)步驟來(lái)保證正確的性:

  1. 定義你的 designated initializer,確保調(diào)用了直接超類(lèi)的 designated initializer
  2. 重載直接超類(lèi)的 designated initializer。調(diào)用你的新的 designated initializer.
  3. 為新的 designated initializer 寫(xiě)文檔

很多開(kāi)發(fā)者忽略了后兩步,這不僅僅是一個(gè)粗心的問(wèn)題,而且這樣違反了框架的規(guī)則,而且可能導(dǎo)致不確定的行為和bug。 讓我們看看正確的實(shí)現(xiàn)的例子:

@implementation ZOCNewsViewController

- (id)initWithNews:(ZOCNews *)news
{
    // call to the immediate superclass's designated initializer
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        _news = news;
    }
    return self;
}

// Override the immediate superclass's designated initializer (重載直接父類(lèi)的  designated initializer)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    // call the new designated initializer
    return [self initWithNews:nil];
}

@end

你沒(méi)重載 initWithNibName:bundle: 而且調(diào)用者決定用這個(gè)方法初始化你的類(lèi)(這是完全合法的)。 initWithNews: 永遠(yuǎn)不會(huì)被調(diào)用,所以導(dǎo)致了不正確的初始化流程,你的類(lèi)特定的初始化邏輯沒(méi)有被執(zhí)行。

即使可以推斷那個(gè)方法是 designate initializer它,但是最好清晰地明確(未來(lái)的你或者其他開(kāi)發(fā)者在改代碼的時(shí)候會(huì)感謝你的)。你應(yīng)該考慮來(lái)用這兩個(gè)策略(不是互斥的):第一個(gè)是你在文檔中明確哪一個(gè)初始化方法是 designated 的,但是最好你可以用編譯器的指令 __attribute__((objc_designated_initializer)) 來(lái)標(biāo)記你的意圖。 用這個(gè)編譯指令的時(shí)候,編譯器回來(lái)幫你。如果你的新的 designate initializer 沒(méi)有調(diào)用你超類(lèi)的 designated initializer,上編譯器會(huì)發(fā)出警告。 然而,當(dāng)沒(méi)有調(diào)用類(lèi)的 designated initializer 的時(shí)候(并且依次提供必要的參數(shù)),并且調(diào)用其他父類(lèi)中的 designated initialize 的時(shí)候,會(huì)變成一個(gè)不可用的狀態(tài)。參考之前的例子,當(dāng)實(shí)例化一個(gè) ZOCNewsViewController 展示一個(gè)新聞而那條新聞沒(méi)有展示的話,就會(huì)毫無(wú)意義。這個(gè)情況下你應(yīng)該只需要讓其他的 designated initializer 失效,來(lái)強(qiáng)制調(diào)用一個(gè)非常特別的 designated initializer。通過(guò)使用另外一個(gè)編譯器指令 __attribute__((unavailable("Invoke the designated initializer"))) 來(lái)修飾一個(gè)方法,通過(guò)這個(gè)屬性,會(huì)讓你在試圖調(diào)用這個(gè)方法的時(shí)候產(chǎn)生一個(gè)編譯錯(cuò)誤。

這是之前的例子相關(guān)的實(shí)現(xiàn)的頭文件(這里使用宏來(lái)讓代碼沒(méi)有那么啰嗦)


@interface ZOCNewsViewController : UIViewController

- (instancetype)initWithNews:(ZOCNews *)news ZOC_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil ZOC_UNAVAILABLE_INSTEAD(initWithNews:);
- (instancetype)init ZOC_UNAVAILABLE_INSTEAD(initWithNews:);

@end

上述的一個(gè)推論是:你應(yīng)該永遠(yuǎn)不從 designated initializer 里面調(diào)用一個(gè) secondary initializer (如果secondary initializer 遵守約定,它會(huì)調(diào)用 designated initializer)。如果這樣,調(diào)用很可能會(huì)調(diào)用一個(gè)子類(lèi)重寫(xiě)的 init 方法并且陷入無(wú)限遞歸之中。

然而一個(gè)意外是一個(gè)對(duì)象是否遵守 NSCoding 協(xié)議,并且它通過(guò)方法 initWithCoder: 初始化。 我們應(yīng)該區(qū)別超類(lèi)是否符合 NSCoding 的情況。 如果符合,如果你只是調(diào)用 [super initWithCoder:] 你會(huì)可能有一個(gè)共享的初始化代碼在 designated initializer 里面,一個(gè)好的方法是吧這些代碼放在私有方法里面(比如 p_commonInit )。 當(dāng)你的超類(lèi)不符合NSCoding 協(xié)議的時(shí)候,推薦把 initWithCoder: 作為 secondary initializer 來(lái)對(duì)待,并且調(diào)用 self 的 designated initializer。 注意這是違反 Apple 的 Archives and Serializations Programming Guide 上面寫(xiě)的:

the object should first invoke its superclass's designated initializer to initialize inherited state (對(duì)象總是應(yīng)該首先調(diào)用超類(lèi)的 designated initializer 來(lái)初始化繼承的狀態(tài))

如果你的類(lèi)不是 NSObject 的直接子類(lèi),這樣做的話,會(huì)導(dǎo)致不可預(yù)測(cè)的行為。

Secondary Initializer

正如之前的描述么,secondary initializer 是一種方便提供默認(rèn)值、行為到 designated initializer 的 方法。也就是說(shuō),你不應(yīng)該強(qiáng)制很多初始化操作在這樣的方法里面,并且你應(yīng)該一直假設(shè)這個(gè)方法不會(huì)得到調(diào)用。我們保證的是唯一被調(diào)用的方法是 designated initializer。 這意味著你的 designated initializer 總是應(yīng)該調(diào)用其他的 secondary initializer 或者你 self 的 designated initializer。有時(shí)候,因?yàn)殄e(cuò)誤,可能打成了 super,這樣會(huì)導(dǎo)致不符合上面提及的初始化順序(在這個(gè)特別的例子里面,是跳過(guò)當(dāng)前類(lèi)的初始化)

References 參考

instancetype

我們經(jīng)常忽略 Cocoa 充滿了約定,并且這些約定可以幫助編譯器變得更加聰明。無(wú)論編譯器是否遭遇 alloc 或者 init 方法,他會(huì)知道,即使返回類(lèi)型都是 id ,這些方法總是返回接受到的類(lèi)類(lèi)型的實(shí)例。因此,它允許編譯器進(jìn)行類(lèi)型檢查。(比如,檢查方法返回的類(lèi)型是否合法)。Clang的這個(gè)好處來(lái)自于 related result type, 意味著:

messages sent to one of alloc and init methods will have the same static type as the instance of the receiver class (發(fā)送到 alloc 或者 init 方法的消息會(huì)有同樣的靜態(tài)類(lèi)型檢查是否為接受類(lèi)的實(shí)例。)

更多的關(guān)于這個(gè)自動(dòng)定義相關(guān)返回類(lèi)型的約定請(qǐng)查看 Clang Language Extensions guide 的appropriate section

一個(gè)相關(guān)的返回類(lèi)型可以明確地規(guī)定用 instancetype 關(guān)鍵字作為返回類(lèi)型,并且它可以在一些工廠方法或者構(gòu)造器方法的場(chǎng)景下很有用。它可以提示編譯器正確地檢查類(lèi)型,并且更加重要的是,這同時(shí)適用于它的子類(lèi)。

@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name;
@end

雖然如此,根據(jù) clang 的定義,id 可以被編譯器提升到 instancetype 。在 alloc 或者 init 中,我們強(qiáng)烈建議對(duì)所有返回類(lèi)的實(shí)例的類(lèi)方法和實(shí)例方法使用 instancetype 類(lèi)型。

在你的 API 中要構(gòu)成習(xí)慣以及保持始終如一的,此外,通過(guò)對(duì)你代碼的小調(diào)整你可以提高可讀性:在簡(jiǎn)單的瀏覽的時(shí)候你可以區(qū)分哪些方法是返回你類(lèi)的實(shí)例的。你以后會(huì)感謝這些注意過(guò)的小細(xì)節(jié)的。

參考

初始化模式

類(lèi)簇 (class cluster)

類(lèi)簇在Apple的文檔中這樣描述:

an architecture that groups a number of private, concrete subclasses under a public, abstract superclass. (一個(gè)在共有的抽象超類(lèi)下設(shè)置一組私有子類(lèi)的架構(gòu))

如果這個(gè)描述聽(tīng)起來(lái)很熟悉,說(shuō)明你的直覺(jué)是對(duì)的。 Class cluster 是 Apple 對(duì)抽象工廠設(shè)計(jì)模式的稱(chēng)呼。

class cluster 的想法很簡(jiǎn)單,你經(jīng)常有一個(gè)抽象類(lèi)在初始化期間處理信息,經(jīng)常作為一個(gè)構(gòu)造器里面的參數(shù)或者環(huán)境中讀取,來(lái)完成特定的邏輯并且實(shí)例化子類(lèi)。這個(gè)"public facing" 應(yīng)該知曉它的子類(lèi)而且返回適合的私有子類(lèi)。

這個(gè)模式非常有用,因?yàn)樗鼫p少了構(gòu)造器調(diào)用中的復(fù)雜性,只需要知道接口如何與對(duì)象通信,而不需要知道怎么實(shí)現(xiàn)。

Class clusters 在 Apple 的Framework 中廣泛使用:一些明顯的例子比如 NSNumber 可以返回不同哦給你的子類(lèi),取決于 數(shù)字類(lèi)型如何提供 (Integer, Float, etc...) 或者 NSArray 返回不同的最優(yōu)存儲(chǔ)策略的子類(lèi)。

這個(gè)模式的精妙的地方在于,調(diào)用者可以完全不管子類(lèi),事實(shí)上,這可以用在設(shè)計(jì)一個(gè)庫(kù),可以用來(lái)交換實(shí)際的返回的類(lèi),而不用去管相關(guān)的細(xì)節(jié),因?yàn)樗鼈兌甲駨某橄蟪?lèi)的方法。

我們的經(jīng)驗(yàn)是使用類(lèi)簇可以幫助移除很多條件語(yǔ)句。

一個(gè)經(jīng)典的例子是如果你有為 iPad 和 iPhone 寫(xiě)的一樣的 UIViewController 子類(lèi),但是在不同的設(shè)備上有不同的行為。

比較基礎(chǔ)的實(shí)現(xiàn)是用條件語(yǔ)句檢查設(shè)備,然后執(zhí)行不同的邏輯。雖然剛開(kāi)始可能不錯(cuò),但是隨著代碼的增長(zhǎng),運(yùn)行邏輯也會(huì)趨于復(fù)雜。 一個(gè)更好的實(shí)現(xiàn)的設(shè)計(jì)是創(chuàng)建一個(gè)抽象而且寬泛的 view controller 來(lái)包含所有的共享邏輯,并且對(duì)于不同設(shè)備有兩個(gè)特別的子例。

通用的 view controller 會(huì)檢查當(dāng)前設(shè)備并且返回適當(dāng)?shù)淖宇?lèi)。

@implementation ZOCKintsugiPhotoViewController

- (id)initWithPhotos:(NSArray *)photos
{
    if ([self isMemberOfClass:ZOCKintsugiPhotoViewController.class]) {
        self = nil;

        if ([UIDevice isPad]) {
            self = [[ZOCKintsugiPhotoViewController_iPad alloc] initWithPhotos:photos];
        }
        else {
            self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];
        }
        return self;
    }
    return [super initWithNibName:nil bundle:nil];
}

@end

之前的代碼的例子展示了如何創(chuàng)建一個(gè)類(lèi)簇。首先,[self isMemberOfClass:ZOCKintsugiPhotoViewController.class] 來(lái)避免在子類(lèi)中重載初始化方法,來(lái)避免無(wú)限的遞歸。當(dāng) [[ZOCKintsugiPhotoViewController alloc] initWithPhotos:photos] 得到調(diào)用的時(shí)候之前的檢查會(huì)變成 true 的,self = nil 是用來(lái)移除所有到 ZOCKintsugiPhotoViewController 實(shí)例的引用的,它會(huì)被釋放,按照這個(gè)邏輯來(lái)檢查哪個(gè)類(lèi)應(yīng)該被初始化。 讓我們假設(shè)在 iPhone 上運(yùn)行了這個(gè)代碼, ZOCKintsugiPhotoViewController_iPhone 沒(méi)有重載initWithPhotos:,在這個(gè)情況下,當(dāng)執(zhí)行 self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos]; 的時(shí)候,ZOCKintsugiPhotoViewController 會(huì)被調(diào)用,并且當(dāng)?shù)谝淮螜z查的時(shí)候,這樣不會(huì)讓 ZOCKintsugiPhotoViewController 檢查會(huì)變成 false 調(diào)用return [super initWithNibName:nil bundle:nil]; ,這會(huì)讓 繼續(xù)初始化執(zhí)行正確的初始化之前的會(huì)話。

單例

如果可能,請(qǐng)盡量避免使用單例而是依賴注入。 然而,如果一定要用,請(qǐng)使用一個(gè)線程安全的模式來(lái)創(chuàng)建共享的實(shí)例。 對(duì)于GCD,用 dispatch_once() 函數(shù)就可以咯。

+ (instancetype)sharedInstance
{
   static id sharedInstance = nil;
   static dispatch_once_t onceToken = 0;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });
   return sharedInstance;
}

使用dispatch_once(),來(lái)控制代碼同步,取代了原來(lái)老的約定俗成的用法。

+ (instancetype)sharedInstance
{
    static id sharedInstance;
    @synchronized(self) {
        if (sharedInstance == nil) {
            sharedInstance = [[MyClass alloc] init];
        }
    }
    return sharedInstance;
}

dispatch_once() 的優(yōu)點(diǎn)是,它更快,而且語(yǔ)法上更干凈,因?yàn)閐ispatch_once()的意思就是 ”把一些東西執(zhí)行一次“,就像我們做的一樣。 這樣同時(shí)可以避免possible and sometimes prolific crashes.

經(jīng)典的可以接受的單例對(duì)象的例子是一個(gè)設(shè)備的 GPS 以及 動(dòng)作傳感器。即使單例對(duì)象可以被子類(lèi)化,這個(gè)情況可以十分有用。這個(gè)接口應(yīng)該證明給出的類(lèi)是趨向于使用單例的。然而,經(jīng)常使用一個(gè)單獨(dú)的公開(kāi)的 sharedInstance 類(lèi)方法就夠了,并且不可寫(xiě)的屬性也應(yīng)該被暴露。

把單例作為一個(gè)對(duì)象的容器來(lái)在代碼或者應(yīng)用層面上共享是糟糕和丑陋的,這是一個(gè)不好的設(shè)計(jì)。

屬性

屬性應(yīng)該盡可能描述性地命名,避免縮寫(xiě),并且是小寫(xiě)字母開(kāi)頭的駝峰命名。我們的工具可以很方便地幫我們自動(dòng)補(bǔ)全所有東西(嗯。。幾乎所有的,Xcode 的Derived Data 會(huì)索引這些命名)。所以沒(méi)理由少打幾個(gè)字符了,并且最好盡可能在你源碼里表達(dá)更多東西。

例子 :

NSString *text;

不要這樣 :

NSString* text;
NSString * text;

(注意:這個(gè)習(xí)慣和常量不同,這是主要從常用和可讀性考慮。 C++ 的開(kāi)發(fā)者偏好從變量名中分離類(lèi)型,作為類(lèi)型它應(yīng)該是 NSString* (對(duì)于從堆中分配的對(duì)象,同事對(duì)于C++是不能從棧上分配的)格式。)

使用屬性的自動(dòng)同步 (synthesize) 而不是手動(dòng)的 @synthesize 語(yǔ)句,除非你的屬性是 protocol 的一部分而不是一個(gè)完整的類(lèi)。如果 Xcode 可以自動(dòng)同步這些變量,就讓它來(lái)做吧。否則只會(huì)讓你拋開(kāi) Xcode 的優(yōu)點(diǎn),維護(hù)更冗長(zhǎng)的代碼。

你應(yīng)該總是使用 setter 和 getter 方法訪問(wèn)屬性,除了 initdealloc 方法。通常,使用屬性讓你增加了在當(dāng)前作用域之外的代碼塊的可能所以可能帶來(lái)更多副作用

你總應(yīng)該用 getter 和 setter 因?yàn)椋?/p>

  • 使用 setter 會(huì)遵守定義的內(nèi)存管理語(yǔ)義(strong, weak, copy etc...) 這回定義更多相關(guān)的在ARC是錢(qián),因?yàn)樗冀K是相關(guān)的。舉個(gè)例子,copy 每個(gè)時(shí)候你用 setter 并且傳送數(shù)據(jù)的時(shí)候,它會(huì)復(fù)制數(shù)據(jù)而不用額外的操作
  • KVO 通知(willChangeValueForKey, didChangeValueForKey) 會(huì)被自動(dòng)執(zhí)行
  • 更容易debug:你可以設(shè)置一個(gè)斷點(diǎn)在屬性聲明上并且斷點(diǎn)會(huì)在每次 getter / setter 方法調(diào)用的時(shí)候執(zhí)行,或者你可以在自己的自定義 setter/getter 設(shè)置斷點(diǎn)。
  • 允許在一個(gè)單獨(dú)的地方為設(shè)置值添加額外的邏輯。

你應(yīng)該傾向于用 getter:

  • 它是對(duì)未來(lái)的變化有擴(kuò)展能力的(比如,屬性是自動(dòng)生成的)
  • 它允許子類(lèi)化
  • 更簡(jiǎn)單的debug(比如,允許拿出一個(gè)斷點(diǎn)在 getter 方法里面,并且看誰(shuí)訪問(wèn)了特別的 getter
  • 它讓意圖更加清晰和明確:通過(guò)訪問(wèn) ivar _anIvar 你可以明確的訪問(wèn) self->_anIvar.這可能導(dǎo)致問(wèn)題。在 block 里面訪問(wèn) ivar (你捕捉并且 retain 了 sefl 即使你沒(méi)有明確的看到 self 關(guān)鍵詞)
  • 它自動(dòng)產(chǎn)生KVO 通知
  • 在消息發(fā)送的時(shí)候增加的開(kāi)銷(xiāo)是微不足道的。更多關(guān)于新年問(wèn)題的介紹你可以看 Should I Use a Property or an Instance Variable?

Init 和 Dealloc

有一個(gè)例外:你永遠(yuǎn)不能在 init (以及其他初始化函數(shù))里面用 getter 和 setter 方法,并且你直接訪問(wèn)實(shí)例變量。事實(shí)上一個(gè)子類(lèi)可以重載sette或者getter并且嘗試調(diào)用其他方法,訪問(wèn)屬性的或者 ivar 的話,他們可能沒(méi)有完全初始化。記住一個(gè)對(duì)象是僅僅在 init 返回的時(shí)候,才會(huì)被認(rèn)為是初始化完成到一個(gè)狀態(tài)了。

同樣在 dealloc 方法中(在 dealloc 方法中,一個(gè)對(duì)象可以在一個(gè) 不確定的狀態(tài)中)這是同樣需要被注意的。

此外,在 init 中使用 setter 不會(huì)很好執(zhí)行 UIAppearence 代理(參見(jiàn) UIAppearance for Custom Views 看更多相關(guān)信息).)

點(diǎn)符號(hào)

當(dāng)使用 setter getter 方法的時(shí)候盡量使用點(diǎn)符號(hào)。應(yīng)該總是用點(diǎn)符號(hào)來(lái)訪問(wèn)以及設(shè)置屬性

例子:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

不要這樣:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

使用點(diǎn)符號(hào)會(huì)讓表達(dá)更加清晰并且?guī)椭鷧^(qū)分屬性訪問(wèn)和方法調(diào)用

屬性定義

推薦按照下面的格式來(lái)定義屬性

@property (nonatomic, readwrite, copy) NSString *name;

屬性的參數(shù)應(yīng)該按照下面的順序排列: 原子性,讀寫(xiě) 和 內(nèi)存管理。 這樣做你的屬性更容易修改正確,并且更好閱讀。

你必須使用 nonatomic,除非特別需要的情況。在iOS中,atomic帶來(lái)的鎖特別影響性能。

屬性可以存儲(chǔ)一個(gè)代碼塊。為了讓它存活到定義的塊的結(jié)束,必須使用 copy (block 最早在棧里面創(chuàng)建,使用 copy讓 block 拷貝到堆里面去)

為了完成一個(gè)共有的 getter 和一個(gè)私有的 setter,你應(yīng)該聲明公開(kāi)的屬性為 readonly 并且在類(lèi)擴(kuò)展總重新定義通用的屬性為 readwrite 的。

@interface MyClass : NSObject
@property (nonatomic, readonly) NSObject *object
@end

@implementation MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object
@end

如果 BOOL 屬性的名字是描述性的,這個(gè)屬性可以省略 "is" ,但是特定要在 get 訪問(wèn)器中指定名字,如:

@property (assign, getter=isEditable) BOOL editable;

文字和例子是引用 Cocoa Naming Guidelines.

為了避免 @synthesize 的使用,在實(shí)現(xiàn)文件中,Xcode已經(jīng)自動(dòng)幫你添加了。

私有屬性

私有屬性應(yīng)該在類(lèi)實(shí)現(xiàn)文件的類(lèi)拓展(class extensions,沒(méi)有名字的 categories 中)中。有名字的 categories(如果 ZOCPrivate)不應(yīng)該使用,除非拓展另外的類(lèi)。

例子:

@interface ZOCViewController ()
@property (nonatomic, strong) UIView *bannerView;
@end

可變對(duì)象

【疑問(wèn)】

任何可以用來(lái)用一個(gè)可變的對(duì)象設(shè)置的((比如 NSString,NSArray,NSURLRequest))屬性的的內(nèi)存管理類(lèi)型必須是 copy 的。

這個(gè)是用來(lái)確保包裝,并且在對(duì)象不知道的情況下避免改變值。

你應(yīng)該同時(shí)避免暴露在公開(kāi)的接口中可變的對(duì)象,因?yàn)檫@允許你的類(lèi)的使用者改變你自己的內(nèi)部表示并且破壞了封裝。你可以提供可以只讀的屬性來(lái)返回你對(duì)象的不可變的副本。

/* .h */
@property (nonatomic, readonly) NSArray *elements

/* .m */
- (NSArray *)elements {
  return [self.mutableElements copy];
}

懶加載

當(dāng)實(shí)例化一個(gè)對(duì)象可能耗費(fèi)很多資源的,或者需要只配置一次并且有一些配置方法需要調(diào)用,而且你還不想弄亂這些方法。

在這個(gè)情況下,我們可以選擇使用重載屬性的 getter 方法來(lái)做 lazy 實(shí)例化。通常這種操作的模板像這樣:

- (NSDateFormatter *)dateFormatter {
  if (!_dateFormatter) {
    _dateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        [dateFormatter setLocale:enUSPOSIXLocale];
        [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSS"];
  }
  return _dateFormatter;
}

即使在一些情況下這是有益的,但是我們?nèi)匀唤ㄗh你在決定這樣做之前經(jīng)過(guò)深思熟慮,事實(shí)上這樣是可以避免的。下面是使用 延遲實(shí)例化的爭(zhēng)議。

  • getter 方法不應(yīng)該有副作用。在使用 getter 方法的時(shí)候你不要想著它可能會(huì)創(chuàng)建一個(gè)對(duì)象或者導(dǎo)致副作用,事實(shí)上,如果調(diào)用 getter 方法的時(shí)候沒(méi)有涉及返回的對(duì)象,編譯器就會(huì)放出警告:getter 不應(yīng)該產(chǎn)生副作用
  • 你在第一次訪問(wèn)的時(shí)候改變了初始化的消耗,產(chǎn)生了副作用,這回讓優(yōu)化性能變得困難(以及測(cè)試)
  • 這個(gè)初始化可能是不確定的:比如你期望屬性第一次被一個(gè)方法訪問(wèn),但是你改變了類(lèi)的實(shí)現(xiàn),訪問(wèn)器在你預(yù)期之前就得到了調(diào)用,這樣可以導(dǎo)致問(wèn)題,特別是初始化邏輯可能依賴于類(lèi)的其他不同狀態(tài)的時(shí)候。總的來(lái)說(shuō)最好明確依賴關(guān)系。
  • 這個(gè)行為不是 KVO 友好的。如果 getter 改變了引用,他應(yīng)該通過(guò)一個(gè) KVO 通知來(lái)通知改變。當(dāng)訪問(wèn) getter 的時(shí)候收到一個(gè)改變的通知很奇怪。

方法

參數(shù)斷言

你的方法可能要求一些參數(shù)來(lái)滿足特定的條件(比如不能為nil),在這種情況下啊最好使用 NSParameterAssert() 來(lái)斷言條件是否成立或是拋出一個(gè)異常。

私有方法

永遠(yuǎn)不要在你的私有方法前加上 _ 前綴。這個(gè)前綴是 Apple 保留的。不要冒重載蘋(píng)果的私有方法的險(xiǎn)。

相等性

當(dāng)你要實(shí)現(xiàn)相等性的時(shí)候記住這個(gè)約定:你需要同時(shí)實(shí)現(xiàn)isEqual and the hash方法。如果兩個(gè)對(duì)象是被isEqual認(rèn)為相等的,它們的 hash 方法需要返回一樣的值。但是如果 hash 返回一樣的值,并不能確保他們相等。

這個(gè)約定是因?yàn)楫?dāng)被存儲(chǔ)在集合(如 NSDictionaryNSSet 在底層使用 hash 表數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu))的時(shí)候,如何查找這些對(duì)象。

@implementation ZOCPerson

- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }

    if (![object isKindOfClass:[ZOCPerson class]]) {
        return NO;
    }

    // check objects properties (name and birthday) for equality
    ...
    return propertiesMatch;
}

- (NSUInteger)hash {
    return [self.name hash] ^ [self.birthday hash];
}

@end

一定要注意 hash 方法不能返回一個(gè)常量。這是一個(gè)典型的錯(cuò)誤并且會(huì)導(dǎo)致嚴(yán)重的問(wèn)題,因?yàn)槭褂昧诉@個(gè)值作為 hash 表的 key,會(huì)導(dǎo)致 hash 表 100%的碰撞

你總是應(yīng)該用 isEqualTo<#class-name-without-prefix#>: 這樣的格式實(shí)現(xiàn)一個(gè)相等性檢查方法。如果你這樣做,會(huì)優(yōu)先調(diào)用這個(gè)方法來(lái)避免上面的類(lèi)型檢查。

一個(gè)完整的 isEqual* 方法應(yīng)該是這樣的:

- (BOOL)isEqual:(id)object {
    if (self == object) {
      return YES;
    }

    if (![object isKindOfClass:[ZOCPerson class]]) {
      return NO;
    }

    return [self isEqualToPerson:(ZOCPerson *)object];
}

- (BOOL)isEqualToPerson:(Person *)person {
    if (!person) {
        return NO;
    }

    BOOL namesMatch = (!self.name && !person.name) ||
                       [self.name isEqualToString:person.name];
    BOOL birthdaysMatch = (!self.birthday && !person.birthday) ||
                           [self.birthday isEqualToDate:person.birthday];

  return haveEqualNames && haveEqualBirthdays;
}

一個(gè)對(duì)象實(shí)例的 hash 計(jì)算結(jié)果應(yīng)該是確定的。當(dāng)它被加入到一個(gè)容器對(duì)象(比如 NSArray, NSSet, 或者 NSDictionary)的時(shí)候這是很重要的,否則行為會(huì)無(wú)法預(yù)測(cè)(所有的容器對(duì)象使用對(duì)象的 hash 來(lái)查找或者實(shí)施特別的行為,如確定唯一性)這也就是說(shuō),應(yīng)該用不可變的屬性來(lái)計(jì)算 hash 值,或者,最好保證對(duì)象是不可變的。

上一篇:美化代碼下一篇:面向切面編程