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

我們在構(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)問題了:

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

符號(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)為其中的名稱加上前綴。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容