我們?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)題了:
符號(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)為其中的名稱加上前綴尺锚。