第四章 協(xié)議與分類—第27條:使用"class-continuation分類"隱藏實(shí)現(xiàn)細(xì)節(jié)

類中經(jīng)常會(huì)包含一些無(wú)須對(duì)外公布的方法及實(shí)例變量。其實(shí)這些內(nèi)容也可以對(duì)外公布庵寞,并且寫(xiě)明其為私有枉证,開(kāi)發(fā)者不應(yīng)依賴它們以躯。Objective-C動(dòng)態(tài)消息系統(tǒng)(參見(jiàn)第11條)的工作方式?jīng)Q定了其不可能實(shí)現(xiàn)真正的私有方法或私有實(shí)例變量刮吧。然而湖饱,我們最好還是只把確實(shí)需要對(duì)外公布的那部分內(nèi)容公開(kāi)。那么杀捻,這種不需要對(duì)外公布但卻應(yīng)該具有的方法及實(shí)例變量應(yīng)該怎么寫(xiě)呢井厌?此時(shí),這個(gè)特殊的"class-continuation分類"就派上用場(chǎng)了致讥。
"class-continuation分類"和普通的分類不同仅仆,它必須定義在其所接續(xù)的那個(gè)類的實(shí)現(xiàn)文件里。其重要之處在于垢袱,這是唯一能聲明的實(shí)例變量的分類墓拜,而且此分類沒(méi)有特定的實(shí)現(xiàn)文件,其中的方法都應(yīng)該定義在類的主實(shí)現(xiàn)文件里请契。與其他分類不同咳榜,"class-continuation分類"沒(méi)有名字。比如爽锥,有個(gè)類叫做EOCPerson涌韩,其"class-continuation分類"寫(xiě)法如下:

@interface EOCPerson ()
//Methods here
@end

為什么需要有這種分類呢?因?yàn)槠渲锌梢远x方法和實(shí)現(xiàn)變量氯夷。為什么能在其中定義方法和實(shí)例變量呢臣樱?只因有"穩(wěn)固的ABI"這一機(jī)制(第6條詳解了此機(jī)制),使得我們無(wú)須知道對(duì)象大小即可使用它腮考。由于類的使用者不一定需要知道實(shí)例變量的內(nèi)存布局雇毫,所以,它們也就未必非得定義在公共接口中了踩蔚∽炻#基于上述原因,我們可以像在類的實(shí)現(xiàn)文件里那樣寂纪,于"class-continuation分類"中給類新增實(shí)例變量席吴。只需在適當(dāng)位置上多寫(xiě)幾個(gè)括號(hào),然后把實(shí)例變量放進(jìn)去:

@interface ECOPerson() {
      NSString *_anInstanceVariable;
}
//Method declarations here
@end

@implementation EOCPerson {
      int _anotherInstanceVariable;
}
//Method implementations here
@end

這樣做有什么好處呢?公共接口里本來(lái)就能定義實(shí)例變量捞蛋。不過(guò)孝冒,把它們定義在"class-continuation分類"或"實(shí)現(xiàn)塊"中可以將其隱藏起來(lái),只供本類使用拟杉。即便在公共接口里將其標(biāo)注為private庄涡,也還是會(huì)泄漏實(shí)現(xiàn)細(xì)節(jié)。比方說(shuō)搬设,你有個(gè)絕密的類穴店,不想給其他人知道撕捍。假設(shè)你所寫(xiě)的某個(gè)類擁有那個(gè)絕密類的實(shí)例,而這個(gè)實(shí)例變量又聲明在公共接口里面:

#import <Foundation/Foundation.h>

@class EOCSuperSecretClass

@interface EOCClass : NSObject {
@private
    EOCSuperSecretClass *_secretInstance;
}
@end

那么泣洞,信息就泄漏了忧风,別人就會(huì)知道有個(gè)名叫EOCSuperSecretClass的類。為解決此問(wèn)題球凰,可以不把實(shí)例變量聲明為強(qiáng)類型狮腿,而是將其類型由EOCSuperSecretClass*改為id。然而這么做不夠好呕诉,因?yàn)樵陬惖膬?nèi)部使用此實(shí)例時(shí)缘厢,無(wú)法得到編譯器的幫助。沒(méi)必要只因?yàn)橄雽?duì)外界隱藏某個(gè)內(nèi)容就放棄編譯器的輔助檢查功能吧甩挫?這個(gè)問(wèn)題可以由"class-continuation分類"來(lái)解決贴硫。那個(gè)代表絕密類的實(shí)例可以聲明成這樣:

// EOCClass.h
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
@end

// EOCClass.m
#import "EOCClass.h"
#import "EOCSuperSecretClass.h"

@interface EOCClass () {
    EOCSuperSecretClass *_secretInstance;
}
@end

@implementation EOCClass
// Methods here
@end

實(shí)例變量也可以定義在實(shí)現(xiàn)塊里,從語(yǔ)法上說(shuō)伊者,這與直接添加到"class-continuation分類"等效英遭,只是看個(gè)人喜好了。筆者喜歡將其添加在"class-continuation分類"中删壮,以便將全部數(shù)據(jù)定義都放在一處贪绘。由于"class-continuation分類"里還能定義一些屬性兑牡,所以在這里額外聲明一些實(shí)例變量也很合適央碟。這些實(shí)例變量并非真的私有,因?yàn)樵谶\(yùn)行期總可以調(diào)用某些方法繞過(guò)此限制均函,不過(guò)亿虽,從一般意義上來(lái)說(shuō),它們還是私有的苞也。此外洛勉,由于沒(méi)有聲明在公共頭文件里,所以將代碼作為程序庫(kù)的一部分來(lái)發(fā)行時(shí)如迟,其隱藏程度更好收毫。
編寫(xiě)Objective-C代碼時(shí)"class-continuation分類"也尤為有用。Objective-C++是Objective-C與C++的混合體殷勘,其代碼可以用這兩種語(yǔ)言來(lái)編寫(xiě)此再。由于兼容性原因,游戲后端一般用C++來(lái)寫(xiě)玲销。另外输拇,有時(shí)候要使用的第三方庫(kù)可能只有C++綁定,此時(shí)也必須使用C++來(lái)編碼贤斜。在這些情況下策吠,使用"class-continuation分類"會(huì)很方便逛裤。假設(shè)某個(gè)類打算這樣寫(xiě):

#import <Foundation/Foundation.h>

#include "SomeCppClass.h"

@interface EOCClass : NSObject {
@private
    SomeCppClass _cppClass;
}
@end

該類的實(shí)現(xiàn)文件可能叫做EOCClass.mm,其中.mm擴(kuò)展名表示編譯器應(yīng)該將此文件按Objective-C++來(lái)編譯猴抹,否則带族,就無(wú)法正確引入SomeCppClass.h了。然而請(qǐng)注意洽糟,名為SomeCppClass的這個(gè)C++類必須完全引入炉菲,因?yàn)榫幾g器要完整地解析其定義方能得知_cppClass實(shí)例變量的大小。于是坤溃,只要是包含EOCClass.h的類拍霜,都必須編譯為Objective-C++才行,因?yàn)樗鼈兌家肓薙omeCppClass類的頭文件薪介。這很快就會(huì)失控祠饺,最終導(dǎo)致整個(gè)應(yīng)用程序全部都要編譯為Objective-C++。這么做確實(shí)完全可行汁政,不過(guò)筆者覺(jué)得相當(dāng)別扭道偷,尤其是將代碼發(fā)布為程序庫(kù)供其他應(yīng)用程序使用時(shí),更不應(yīng)該如此记劈。要求第三方開(kāi)發(fā)者將其源文件擴(kuò)展名均改為.mm不是很合適勺鸦。
你可能認(rèn)為解決此問(wèn)題的辦法是:不引入C++類的頭文件,只是向前聲明該類目木,并且將實(shí)例變量做成指向此類的指針换途。

#import <Foundation/Foundation.h>

class SomeCppClass;

@interface EOCClass : NSObject {
@private
    SomeCppClass *_cppClass;
}
@end

現(xiàn)在實(shí)例變量必須是指針,若不是刽射,則編譯器無(wú)法得知其大小军拟,從而會(huì)報(bào)錯(cuò)。但所有指針的大小確實(shí)都是固定的誓禁,于是編譯器只需知道其所指的類型即可懈息。不過(guò),這么做還是會(huì)遇到剛才那個(gè)問(wèn)題摹恰,因?yàn)橐隕OCClass頭文件的源碼里都包含class關(guān)鍵字辫继,而這是C++的關(guān)鍵字,所以仍然需要按Objective-C++來(lái)編譯才行俗慈。這樣做既別扭又無(wú)必要姑宽,因?yàn)樵搶?shí)例變量畢竟是private的,其他類為什么要知道它呢姜盈?這個(gè)問(wèn)題還是得用"class-continuation分類"來(lái)解決低千。將剛才那個(gè)類改寫(xiě)之后,其代碼如下:

#import <Foundation/Foundation.h>

@interface EOCClass : NSObject
@end

// EOCClass.mm
#import "EOCClass.h"
#include "SomeCppClass.h"

@interface EOCClass () {
    SomeCppClass _cppClass;
}
@end

@implementation EOCClass
@end

改寫(xiě)后的EOCClass類,其頭文件里就沒(méi)有C++代碼了示血,使用頭文件的人甚至意識(shí)不到其底層實(shí)現(xiàn)代碼中混有C++成分棋傍。某些系統(tǒng)庫(kù)用到了這種模式,比如網(wǎng)頁(yè)瀏覽器框架WebKit难审,其大部分代碼都以C++編寫(xiě)瘫拣,然而對(duì)外展示出來(lái)的卻是一套整潔的Objective-C接口。CoreAnimation里面也用到了此模式告喊,它的許多后端代碼都用C++寫(xiě)成麸拄,但對(duì)外公布的卻是一套純Objective-C接口。
"class-continuation分類"還有一種合理用法黔姜,就是將public接口中聲明為"只讀"的屬性擴(kuò)展為"可讀寫(xiě)"拢切,以便在類的內(nèi)部設(shè)置其值。我們通常不直接訪問(wèn)實(shí)例變量秆吵,而是通過(guò)設(shè)置訪問(wèn)方法來(lái)做(參見(jiàn)第7條)淮椰,因?yàn)檫@樣能夠觸發(fā)"鍵值觀測(cè)"(Key-Value Observing, KVO)通知纳寂,其他對(duì)象有可能正監(jiān)聽(tīng)此事件主穗。出現(xiàn)在"class-continuation分類"或其他類中的屬性必須同類接口里的屬性具備相同的特質(zhì)(attribute),不過(guò)毙芜,其"只讀"狀態(tài)可以擴(kuò)充為"可讀寫(xiě)"忽媒。例如,有個(gè)描述個(gè)人信息的類腋粥,其公共接口如下:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName:(NSString*)firstName
               lastName:(NSString*)lastName;
@end

我們一般會(huì)在"class-continuation分類"中把這兩個(gè)屬性擴(kuò)展為"可讀寫(xiě)":

@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end

只需要用上面幾行代碼就行了』抻辏現(xiàn)在EOCPerson的實(shí)現(xiàn)代碼可以隨意調(diào)用"setFirstName:"或"setLastName:"這兩個(gè)設(shè)置方法,也可以用"點(diǎn)語(yǔ)法"來(lái)設(shè)置屬性灯抛。這樣做很有用金赦,既能令外界無(wú)法修改對(duì)象音瓷,又能在其內(nèi)部按照需要管理其數(shù)據(jù)对嚼。這樣,封裝在類中的數(shù)據(jù)就由實(shí)例本身來(lái)控制绳慎,而外部代碼則無(wú)法修改其值纵竖。第18條曾詳述了這一話題。請(qǐng)注意杏愤,若觀察者(observer)正讀取屬性值而內(nèi)部代碼又在寫(xiě)入該屬性時(shí)靡砌,則有可能引發(fā)"競(jìng)爭(zhēng)條件"(race condition)。合理使用同步機(jī)制(參見(jiàn)第41條)能緩解此問(wèn)題珊楼。
只會(huì)在類的實(shí)現(xiàn)代碼中用到的私有方法也可以聲明在"class-continuation分類"中通殃。這么做比較合適,因?yàn)樗枋隽四切┲辉陬悓?shí)現(xiàn)代碼中才會(huì)使用的方法。這些方法可以這樣寫(xiě):

@interface EOCPerson()
- (void)p_privateMethod;
@end

此處根據(jù)第20條所述的建議為方法名加了前綴画舌,以體現(xiàn)其為私有方法堕担。新版編譯器不強(qiáng)制要求開(kāi)發(fā)者在使用方法之前必須先聲明。然而像上面這樣在"class-continuation分類"中聲明一下通常還是有好處的曲聂,因?yàn)檫@樣做可以把類里所含的相關(guān)方法都統(tǒng)一描述于此霹购。筆者在編寫(xiě)類的實(shí)現(xiàn)代碼之前,經(jīng)常喜歡像這樣先把方法原型寫(xiě)出來(lái)朋腋,然后在逐個(gè)實(shí)現(xiàn)齐疙。要想使類的代碼更易讀懂,可以試試這個(gè)好辦法旭咽。
最后還要講一種用法: 若對(duì)象所遵從的協(xié)議只應(yīng)視為私有贞奋,則可在"class-continuation分類"中聲明。有時(shí)由于對(duì)象所遵從的某個(gè)協(xié)議在私有API中穷绵,所以我們可能不太想在公共接口中泄漏這一信息忆矛。比方說(shuō),EOCPerson遵從了名為EOCSecretDelegate的協(xié)議请垛。如果聲明在公共接口里催训,那么要像下面這樣寫(xiě):

#import <Foundation/Foundation.h>
#import "EOCSecretDelegate.h"

@interface EOCPerson : NSObject <EOCSecretDelegate>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName:(NSString*)firstName
               lastName:(NSString*)lastName;
@end

你可能會(huì)說(shuō),只需要向前聲明EOCSecretDelegate協(xié)議就可以不引入它了(或者說(shuō)宗收,不引入定義該協(xié)議的頭文件了)漫拭。用下面這行向前聲明語(yǔ)句來(lái)取代#import指令:

@protocol EOCSecretDelegate;

但是這樣一來(lái),只要引入EOCPerson頭文件的地方混稽,編譯器都會(huì)給出下列警告信息:

warning: cannot find protocol definition for 'EOCSecretDelegate'

由于編譯器看不到協(xié)議的定義采驻,所以無(wú)法得知其中所含的方法,于是就會(huì)像這樣警告開(kāi)發(fā)者匈勋。然而礼旅,這畢竟是個(gè)私有的內(nèi)部協(xié)議,你甚至連名字都不想給別人知道洽洁。此時(shí)還得請(qǐng)"class-continuation分類"來(lái)幫忙痘系。不要在公共接口中聲明EOCPerson類遵從了EOCSecretDelegate協(xié)議,而是改到"class-continuation分類"里面聲明:

#import "EOCPerson.h"
#import "EOCSecretDelegate.h"

@interface EOCPerson () <EOCSecretDelegate>
@end

@implementation EOCPerson
…
@end

公共接口內(nèi)所有提到EOCSecretDelegate的地方都可刪去饿自。這個(gè)私有協(xié)議現(xiàn)在已經(jīng)不為外界所知了汰翠,使用EOCPerson的人若不深入探索一番,則很難發(fā)現(xiàn)其身影昭雌。

要點(diǎn)

  • 通過(guò)"class-continuation分類"向類中新增實(shí)例變量
    -如果某屬性在主接口中聲明為"只讀"复唤,而類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在"class-continuation分類"中將其擴(kuò)展為"可讀寫(xiě)"烛卧。
  • 把私有方法的原型聲明在"class-continuation分類"里面佛纫。
  • 若想使類所遵循的協(xié)議不為人所知,則可于"class-continuation分類"中聲明。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呈宇,一起剝皮案震驚了整個(gè)濱河市跟磨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攒盈,老刑警劉巖抵拘,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異型豁,居然都是意外死亡僵蛛,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)迎变,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)充尉,“玉大人,你說(shuō)我怎么就攤上這事衣形⊥障溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵谆吴,是天一觀的道長(zhǎng)倒源。 經(jīng)常有香客問(wèn)我,道長(zhǎng)句狼,這世上最難降的妖魔是什么笋熬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮腻菇,結(jié)果婚禮上胳螟,老公的妹妹穿的比我還像新娘。我一直安慰自己筹吐,他們只是感情好糖耸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著丘薛,像睡著了一般嘉竟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榔袋,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天周拐,我揣著相機(jī)與錄音铡俐,去河邊找鬼凰兑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛审丘,可吹牛的內(nèi)容都是我干的吏够。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锅知!你這毒婦竟也來(lái)了播急?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤售睹,失蹤者是張志新(化名)和其女友劉穎桩警,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體昌妹,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捶枢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了飞崖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烂叔。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖固歪,靈堂內(nèi)的尸體忽然破棺而出蒜鸡,到底是詐尸還是另有隱情,我是刑警寧澤牢裳,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布逢防,位于F島的核電站,受9級(jí)特大地震影響蒲讯,放射性物質(zhì)發(fā)生泄漏胞四。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一伶椿、第九天 我趴在偏房一處隱蔽的房頂上張望辜伟。 院中可真熱鬧,春花似錦脊另、人聲如沸导狡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旱捧。三九已至,卻和暖如春踩麦,著一層夾襖步出監(jiān)牢的瞬間枚赡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工谓谦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贫橙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓反粥,卻偏偏與公主長(zhǎng)得像卢肃,于是被迫代替她去往敵國(guó)和親疲迂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容