我們在構(gòu)建應(yīng)用程序時(shí),可能想將其中部分代碼用于后續(xù)項(xiàng)目,也可能想把某些代碼發(fā)布出來,供他人使用。即便現(xiàn)在還不想這么做,將來也總會(huì)有用到的時(shí)候。如果決定重用代碼,那么我們在編寫接口時(shí)就會(huì)將其設(shè)計(jì)成易于復(fù)用的形式。這需要用到Objective-C語言中常見的編程范式(paradigm),同時(shí)還需了解各種可能碰到的陷阱。
近年來,開源社區(qū)與開源組件隨著iOS開發(fā)而流行起來,所以我們經(jīng)常會(huì)在開發(fā)自己的應(yīng)用程序時(shí)使用他人所寫的代碼。與此同時(shí),別人也會(huì)用到你的代碼,所以,要把代碼寫的清晰一些,以便其他開發(fā)者能夠迅速而方便地將其集成到他們的項(xiàng)目里。沒準(zhǔn)會(huì)有成千上萬個(gè)應(yīng)用程序使用你所寫的程序庫呢。
Objective-C沒有其他語言那種內(nèi)置的命名空間(namespace)機(jī)制。鑒于此,我們在起名時(shí)要設(shè)法避免潛在的命名沖突,否則很容易就重名了。如果發(fā)生命名沖突(naming clash),那么應(yīng)用程序的鏈接過程就會(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ú)立的程序庫都引入到當(dāng)前項(xiàng)目中,而它們又恰好有重名的類,所以產(chǎn)生了這一問題。
比無法鏈接更糟糕的情況是,在運(yùn)行期載入了含有重名類的程序庫。此時(shí),"動(dòng)態(tài)加載器"(dynamic loader)就遭遇了"重名符號(hào)錯(cuò)誤"(duplicate symbol error),很可能會(huì)令整個(gè)應(yīng)用程序崩潰。
避免此問題的唯一辦法就是變相實(shí)現(xiàn)命名空間: 為所有名稱都加上適當(dāng)前綴。所選前綴可以使公司、應(yīng)用程序或二者皆有關(guān)聯(lián)之名。比方說,假設(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ì)出問題。iOS5.0 SDK發(fā)布時(shí),包含了Twitter框架,此框架就使用TW作前綴,其中有個(gè)類叫做TWRequest,它可以發(fā)送HTTP請(qǐng)求以調(diào)用Twitter API。如果你所在的公司叫做Tiny Widgets,那么很有可能把訪問本公司API所用的那個(gè)類也命名為TWRequest。
不僅是命名,應(yīng)用程序中的所有名稱都應(yīng)加前綴。如果要為既有類新增"分類"(category),那么一定要給"分類"及"分類"中的方法加上前綴,第25條解釋了這么做的原因。開發(fā)者可能會(huì)忽視另外一個(gè)容易引發(fā)命名沖突的地方,那就是類的實(shí)現(xiàn)文件中所用的純C函數(shù)及全局變量,這個(gè)問題必須要注意。大家可別忘了,在編譯好的目標(biāo)文件中,這些名稱是要算作"頂級(jí)符號(hào)"(top-level symbol)的。比方說,iOS SDK的AudioToolbox里有個(gè)函數(shù)能播放聲音文件。開發(fā)者可向其傳入回調(diào)函數(shù)(callback),以便在播放完畢時(shí)調(diào)用。你也許想編寫一個(gè)Objective-C類,把這套邏輯封裝起來,當(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
}
這段代碼看上去完全正常,不過你再看看該類目標(biāo)文件中的符號(hào)表(symbol table),就會(huì)發(fā)現(xiàn)問題了:

符號(hào)表中間有個(gè)名叫_completion的符號(hào),這就是為了處理聲音播放完畢之后的邏輯而創(chuàng)建的那個(gè)completion函數(shù)。雖說此函數(shù)是在實(shí)現(xiàn)文件里定義的,并沒有聲明于頭文件中,不過它仍然算作"頂級(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ā)布為程序庫,供他人在開發(fā)應(yīng)用程序時(shí)使用,那么就更糟糕了。這等于辦了件壞事: 因?yàn)橐呀?jīng)有了名叫_completion的符號(hào),所以使用此程序庫的開發(fā)者就無法再創(chuàng)建名為completion的函數(shù)了。
由此可見,我們總是應(yīng)該給這種C函數(shù)的名字加上前綴。比方說,在剛才那個(gè)例子中,播放完聲音之后所執(zhí)行的處理程序可以改名為EOCSoundPlayerCompletion。這么做還有個(gè)好處:若此符號(hào)出現(xiàn)在?;厮菪畔⒅校瑒t很容易就能判明問題源自哪塊代碼。
如果用第三方庫編寫自己的代碼,并準(zhǔn)備將其再發(fā)布為程序庫供他人開發(fā)應(yīng)用程序所用,那么尤其要注意重復(fù)符號(hào)問題。你的程序庫所包含的那個(gè)第三方庫也許還會(huì)為應(yīng)用程序本身所引入,若是如此,那就很容易出現(xiàn)重復(fù)符號(hào)錯(cuò)誤了。這是應(yīng)該給你所用的那一份第三方庫代碼都加上你自己的前綴。例如,你準(zhǔn)備發(fā)布的程序庫叫做EOCLibrary,其中引入了名為XYZLibrary的第三方庫,那么就應(yīng)該把XYZLibrary中的所有名字都冠以EOC。于是,應(yīng)用程序就可以隨意使用它自己直接引入的那個(gè)XYZLibrary庫了,而不必?fù)?dān)心與EOCLibrary里的這個(gè)XYZLibrary相沖突。
雖說逐個(gè)改名是很令人厭煩的事情,不過若想避免命名沖突,還是得費(fèi)這番功夫才行。讀者也許會(huì)問: 為什么非要這么做呢?應(yīng)用程序自己不要直接引入XYZLibrary,改用EOCLibrary里面的那個(gè)不就行了嗎?沒錯(cuò),可以這么做,但是,應(yīng)用程序也許還會(huì)引入另一個(gè)名為ABCLibrary的第三方庫,而該庫中又包含了XYZLibrary。此時(shí),如果你和ABCLibrary庫的作者都不給各自所用的XYZLibrary加前綴,那么應(yīng)用程序依然會(huì)出現(xiàn)重復(fù)符號(hào)錯(cuò)誤。還有一種可能就是,你的庫里所用的XYZLibrary是X版本的,而應(yīng)用程序卻需要使用Y版本的某些功能,所以它必須自己再引入一份。你可以花些時(shí)間,使用幾個(gè)流行的第三方庫來開發(fā)一下iOS程序,那時(shí)會(huì)經(jīng)??吹竭@種前綴的。
要點(diǎn)
- 選擇與你的公司、應(yīng)用程序或二者皆有關(guān)聯(lián)之名稱作類名的前綴,并在所有代碼中均使用這一前綴。
- 若自己所開發(fā)的程序庫中用到了第三方庫,則應(yīng)為其中的名稱加上前綴。