第三章 接口與API設(shè)計(jì)—第15條:用前綴避免命名空間沖突

我們?cè)跇?gòu)建應(yīng)用程序時(shí),可能想將其中部分代碼用于后續(xù)項(xiàng)目农猬,也可能想把某些代碼發(fā)布出來(lái),供他人使用售淡。即便現(xiàn)在還不想這么做斤葱,將來(lái)也總會(huì)有用到的時(shí)候。如果決定重用代碼揖闸,那么我們?cè)诰帉懡涌跁r(shí)就會(huì)將其設(shè)計(jì)成易于復(fù)用的形式揍堕。這需要用到Objective-C語(yǔ)言中常見(jiàn)的編程范式(paradigm),同時(shí)還需了解各種可能碰到的陷阱汤纸。
近年來(lái)衩茸,開源社區(qū)與開源組件隨著iOS開發(fā)而流行起來(lái),所以我們經(jīng)常會(huì)在開發(fā)自己的應(yīng)用程序時(shí)使用他人所寫的代碼贮泞。與此同時(shí)楞慈,別人也會(huì)用到你的代碼,所以隙畜,要把代碼寫的清晰一些抖部,以便其他開發(fā)者能夠迅速而方便地將其集成到他們的項(xiàng)目里。沒(méi)準(zhǔn)會(huì)有成千上萬(wàn)個(gè)應(yīng)用程序使用你所寫的程序庫(kù)呢议惰。


Objective-C沒(méi)有其他語(yǔ)言那種內(nèi)置的命名空間(namespace)機(jī)制慎颗。鑒于此,我們?cè)谄鹈麜r(shí)要設(shè)法避免潛在的命名沖突言询,否則很容易就重名了俯萎。如果發(fā)生命名沖突(naming clash),那么應(yīng)用程序的鏈接過(guò)程就會(huì)出錯(cuò)运杭,因?yàn)槠渲谐霈F(xiàn)了重復(fù)符號(hào):

duplicate symbol _OBJC_METACLASS_$_EOCTheClass in:
      build/something.o
      build/something_else.o
duplicate symbol _OBJC_CLASS_$_EOCTheClass in:
      build/something.o
      build/something_else.o

錯(cuò)誤原因在于夫啊,應(yīng)用程序中的兩份代碼都各自實(shí)現(xiàn)了名為EOCTheClass的類,這導(dǎo)致EOCTheClass所對(duì)應(yīng)的類符號(hào)和"元類"符號(hào)各定義了兩次辆憔。你也許是把兩個(gè)相互獨(dú)立的程序庫(kù)都引入到當(dāng)前項(xiàng)目中撇眯,而它們又恰好有重名的類报嵌,所以產(chǎn)生了這一問(wèn)題。
比無(wú)法鏈接更糟糕的情況是熊榛,在運(yùn)行期載入了含有重名類的程序庫(kù)锚国。此時(shí),"動(dòng)態(tài)加載器"(dynamic loader)就遭遇了"重名符號(hào)錯(cuò)誤"(duplicate symbol error)玄坦,很可能會(huì)令整個(gè)應(yīng)用程序崩潰血筑。
避免此問(wèn)題的唯一辦法就是變相實(shí)現(xiàn)命名空間: 為所有名稱都加上適當(dāng)前綴。所選前綴可以使公司煎楣、應(yīng)用程序或二者皆有關(guān)聯(lián)之名豺总。比方說(shuō),假設(shè)你所在的公司叫做Effective Widgets择懂,那么就可以在所有應(yīng)用程序都會(huì)用到的那部分代碼中使用EWS作前綴喻喳,如果有些代碼只用于名為Effective Browser的瀏覽器項(xiàng)目中,那就在這部分代碼中使用EWB作前綴困曙。即便加了前綴沸枯,也難保不出現(xiàn)命名沖突,但是其幾率會(huì)小很多赂弓。
使用Cocoa創(chuàng)建應(yīng)用程序時(shí)一定要注意绑榴,Apple宣稱其保留使用所有"兩字母前綴"(two-letter prefix)的權(quán)利,所以你自己選用的前綴應(yīng)該是三個(gè)字母的盈魁。舉個(gè)例子翔怎,假如開發(fā)者不遵循這條守則,使用TW這兩個(gè)字母作前綴杨耙,那么就會(huì)出問(wèn)題赤套。iOS5.0 SDK發(fā)布時(shí),包含了Twitter框架珊膜,此框架就使用TW作前綴容握,其中有個(gè)類叫做TWRequest,它可以發(fā)送HTTP請(qǐng)求以調(diào)用Twitter API车柠。如果你所在的公司叫做Tiny Widgets剔氏,那么很有可能把訪問(wèn)本公司API所用的那個(gè)類也命名為TWRequest。
不僅是命名竹祷,應(yīng)用程序中的所有名稱都應(yīng)加前綴谈跛。如果要為既有類新增"分類"(category),那么一定要給"分類"及"分類"中的方法加上前綴塑陵,第25條解釋了這么做的原因感憾。開發(fā)者可能會(huì)忽視另外一個(gè)容易引發(fā)命名沖突的地方,那就是類的實(shí)現(xiàn)文件中所用的純C函數(shù)及全局變量令花,這個(gè)問(wèn)題必須要注意阻桅。大家可別忘了凉倚,在編譯好的目標(biāo)文件中,這些名稱是要算作"頂級(jí)符號(hào)"(top-level symbol)的嫂沉。比方說(shuō)占遥,iOS SDK的AudioToolbox里有個(gè)函數(shù)能播放聲音文件。開發(fā)者可向其傳入回調(diào)函數(shù)(callback)输瓜,以便在播放完畢時(shí)調(diào)用。你也許想編寫一個(gè)Objective-C類芬萍,把這套邏輯封裝起來(lái)尤揣,當(dāng)播放完聲音文件之后,即命令其中的委托對(duì)象(delegate)處理回調(diào)事宜:

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

@class EOCSoundPlayer;
@protocol EOCSoundPlayerDelegate <NSObject>
- (void)soundPlayerDidFinish:(EOCSoundPlayer *)player;
@end

@Interface EOCSoundPlayer : NSObject
@property (nonatomic, weak) id <EOCSoundPlayerDelegate> delegate;
- (id)initWithURL:(NSURL *)url;
- (void)playSound;
@end
//EOCSoundPlayer.m
#import "EOCSoundPlayer.h"
#import <AudioToolbox/AudioToolbox.h>

void completion (SystemSoundID ssID, void*clientData) {
      EOCSoundPlayer *player = (__bridge EOCSoundPlayer *)clientData;
      if ([player.delegate respondsToSelector:@selector(soundPlayerDidFinish:)]) {
              [player.delegate soundPlayerDidFinish:player];
        }
}

@implementation EOCSoundPlayer {
      SystemSoundID _systemSoundID;
}

- (id)initWithURL:(NSURL *)url {
        if ((self = [super init])) {
              AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &_systemSoundID);
        }
        return self;
}

- (void)dealloc {
 AudioServicesDisposeSystemSound(_systemSoundID);
}

- (void)playSound{
      AudioServicesAddSystemSoundCompletion {
            _systemSoundID,
            NULL,   
            NULL,
            completion,
            (__bridge void *)self;
  AudioServicesPlaySystemSound(_systemSoundID);
      }
      @end
}

這段代碼看上去完全正常柬祠,不過(guò)你再看看該類目標(biāo)文件中的符號(hào)表(symbol table)北戏,就會(huì)發(fā)現(xiàn)問(wèn)題了:

屏幕快照 2017-04-13 09.32.46.png

符號(hào)表中間有個(gè)名叫_completion的符號(hào),這就是為了處理聲音播放完畢之后的邏輯而創(chuàng)建的那個(gè)completion函數(shù)漫蛔。雖說(shuō)此函數(shù)是在實(shí)現(xiàn)文件里定義的嗜愈,并沒(méi)有聲明于頭文件中,不過(guò)它仍然算作"頂級(jí)符號(hào)"莽龟。這樣的話蠕嫁,若在別處又創(chuàng)建了一個(gè)名叫completion的函數(shù),則會(huì)于鏈接時(shí)發(fā)生類似下面這種"重復(fù)符號(hào)錯(cuò)誤":

duplicate symbol _completion in:
        build/EOCSoundPlayer.o
        build/EOCAnotherClass.o

如果將代碼發(fā)布為程序庫(kù)毯盈,供他人在開發(fā)應(yīng)用程序時(shí)使用剃毒,那么就更糟糕了。這等于辦了件壞事: 因?yàn)橐呀?jīng)有了名叫_completion的符號(hào)搂赋,所以使用此程序庫(kù)的開發(fā)者就無(wú)法再創(chuàng)建名為completion的函數(shù)了赘阀。

由此可見(jiàn),我們總是應(yīng)該給這種C函數(shù)的名字加上前綴脑奠。比方說(shuō)基公,在剛才那個(gè)例子中,播放完聲音之后所執(zhí)行的處理程序可以改名為EOCSoundPlayerCompletion宋欺。這么做還有個(gè)好處:若此符號(hào)出現(xiàn)在椇涠梗回溯信息中,則很容易就能判明問(wèn)題源自哪塊代碼齿诞。
如果用第三方庫(kù)編寫自己的代碼秒咨,并準(zhǔn)備將其再發(fā)布為程序庫(kù)供他人開發(fā)應(yīng)用程序所用,那么尤其要注意重復(fù)符號(hào)問(wèn)題掌挚。你的程序庫(kù)所包含的那個(gè)第三方庫(kù)也許還會(huì)為應(yīng)用程序本身所引入雨席,若是如此,那就很容易出現(xiàn)重復(fù)符號(hào)錯(cuò)誤了吠式。這是應(yīng)該給你所用的那一份第三方庫(kù)代碼都加上你自己的前綴陡厘。例如抽米,你準(zhǔn)備發(fā)布的程序庫(kù)叫做EOCLibrary,其中引入了名為XYZLibrary的第三方庫(kù)糙置,那么就應(yīng)該把XYZLibrary中的所有名字都冠以EOC云茸。于是,應(yīng)用程序就可以隨意使用它自己直接引入的那個(gè)XYZLibrary庫(kù)了谤饭,而不必?fù)?dān)心與EOCLibrary里的這個(gè)XYZLibrary相沖突标捺。
雖說(shuō)逐個(gè)改名是很令人厭煩的事情,不過(guò)若想避免命名沖突揉抵,還是得費(fèi)這番功夫才行亡容。讀者也許會(huì)問(wèn): 為什么非要這么做呢?應(yīng)用程序自己不要直接引入XYZLibrary冤今,改用EOCLibrary里面的那個(gè)不就行了嗎闺兢?沒(méi)錯(cuò),可以這么做戏罢,但是屋谭,應(yīng)用程序也許還會(huì)引入另一個(gè)名為ABCLibrary的第三方庫(kù),而該庫(kù)中又包含了XYZLibrary龟糕。此時(shí)桐磁,如果你和ABCLibrary庫(kù)的作者都不給各自所用的XYZLibrary加前綴,那么應(yīng)用程序依然會(huì)出現(xiàn)重復(fù)符號(hào)錯(cuò)誤讲岁。還有一種可能就是所意,你的庫(kù)里所用的XYZLibrary是X版本的,而應(yīng)用程序卻需要使用Y版本的某些功能催首,所以它必須自己再引入一份扶踊。你可以花些時(shí)間,使用幾個(gè)流行的第三方庫(kù)來(lái)開發(fā)一下iOS程序郎任,那時(shí)會(huì)經(jīng)逞砗模看到這種前綴的。

要點(diǎn)

  • 選擇與你的公司舶治、應(yīng)用程序或二者皆有關(guān)聯(lián)之名稱作類名的前綴分井,并在所有代碼中均使用這一前綴。
  • 若自己所開發(fā)的程序庫(kù)中用到了第三方庫(kù)霉猛,則應(yīng)為其中的名稱加上前綴尺锚。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惜浅,隨后出現(xiàn)的幾起案子瘫辩,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伐厌,死亡現(xiàn)場(chǎng)離奇詭異承绸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)挣轨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門军熏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人卷扮,你說(shuō)我怎么就攤上這事荡澎。” “怎么了晤锹?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵摩幔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我抖甘,道長(zhǎng),這世上最難降的妖魔是什么葫慎? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任衔彻,我火速辦了婚禮,結(jié)果婚禮上偷办,老公的妹妹穿的比我還像新娘艰额。我一直安慰自己,他們只是感情好椒涯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布柄沮。 她就那樣靜靜地躺著,像睡著了一般废岂。 火紅的嫁衣襯著肌膚如雪祖搓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天湖苞,我揣著相機(jī)與錄音拯欧,去河邊找鬼。 笑死财骨,一個(gè)胖子當(dāng)著我的面吹牛镐作,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隆箩,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼该贾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了捌臊?” 一聲冷哼從身側(cè)響起杨蛋,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后六荒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體护姆,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年掏击,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卵皂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砚亭,死狀恐怖灯变,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捅膘,我是刑警寧澤添祸,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站寻仗,受9級(jí)特大地震影響刃泌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜署尤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一耙替、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧曹体,春花似錦俗扇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至串稀,卻和暖如春除抛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背母截。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工镶殷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人微酬。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓绘趋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親颗管。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陷遮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)垦江,斷路器帽馋,智...
    卡卡羅2017閱讀 134,672評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,192評(píng)論 25 707
  • 接上篇函數(shù)返回 異常 擴(kuò)充類型的功能 有點(diǎn)像iOS中的分類 js允許給基本類型擴(kuò)充功能。通過(guò)給Object的pro...
    紙簡(jiǎn)書生閱讀 334評(píng)論 0 0
  • 2018年3月3日周六上午10點(diǎn)零基礎(chǔ)新班開課,學(xué)費(fèi)2600/25課時(shí)绽族,上課地點(diǎn):在臺(tái)東體育街羽毛球場(chǎng)兒童...
    楚言初語(yǔ)閱讀 206評(píng)論 0 0
  • 2017是瘋狂的一年 科技的發(fā)展 思想運(yùn)轉(zhuǎn)的速度跟不上發(fā)展的速度 每天都千變?nèi)f化 站在一個(gè)位置 觀賞著BAT在各個(gè)...
    玥燁閱讀 440評(píng)論 0 0