Objective-C 語(yǔ)言的命名空間詳解
“為什么Objecive-C中的很多類名都是NS開頭的呢?”
我保證在你第一次給別人介紹Objective-C的時(shí)候肯定會(huì)聽到這句話。
就像父母要向孩子解釋“什么是死亡”或者“圣誕老人是不存在的”問(wèn)題一樣,父母總是寄希望時(shí)間會(huì)讓孩子自己找到答案。
你既然這么問(wèn)了,實(shí)際上NS代表了NeXTSTEP (好吧,其實(shí)是代表NeXTSTEP/Sun,我們只是做個(gè)簡(jiǎn)單的介紹),它被用于…
你越解釋,你會(huì)發(fā)現(xiàn)對(duì)方越失望,接下來(lái),他們不在只是隨便問(wèn)問(wèn)了,他們開始問(wèn)一些你更難解釋的問(wèn)題–在Objective-C中@是什么?
命名一直是Objective-C的硬傷,和那些優(yōu)雅的語(yǔ)言相比,Objective-C缺乏標(biāo)識(shí)符容器這點(diǎn)引來(lái)了很多不切實(shí)際的批評(píng)家。
他們總是說(shuō):Objective-C不像其他流行語(yǔ)言一樣提供模塊化機(jī)制來(lái)避免類名和方法名的沖突。
相反地,Objective-C 依靠前綴來(lái)確保APP中的一些地方的方法名不會(huì)影響其他的地方有相同名字的代碼。
插入一個(gè)關(guān)于類型系統(tǒng)的題外話之后我們會(huì)繼續(xù)進(jìn)入關(guān)于命名的討論。
C和Objective-C中的類型
我曾在這博客上多次提過(guò),Objective-C是直接建立在C語(yǔ)言之上的,一個(gè)重要的原因是Objective-C和C語(yǔ)言共用一個(gè)類型系統(tǒng),他們都要求標(biāo)識(shí)符是全局唯一的。
你可以自己定義一個(gè)和@interface同名的靜態(tài)變量,編譯之后你會(huì)得到一個(gè)錯(cuò)誤:
- @interface XXObject : NSObject
- @end
- static char * XXObject;//將“XXObject”重新定義為不同的符號(hào)
也就是說(shuō),Objective-C的runtime在C語(yǔ)言的類型系統(tǒng)上又創(chuàng)建了一個(gè)抽象層,它甚至可以允許下面這段代碼被編譯:
- @protocol Foo
- @end
- @interface Foo : NSObject <Foo
- id Foo
- }
- @property id Foo;
- + (id)Foo;
- - (id)Foo;
- @end
- @interface Foo (Foo)
- @end
- @implementation Foo
- @synthesize Foo;
- + (id)Fo
- id Foo = @"Fo
- return Foo
- }
- @end
通過(guò)Objective-C的環(huán)境,程序能區(qū)別所有相同名字的類,協(xié)議,類別,實(shí)例變量,實(shí)例方法和類方法。
一個(gè)變量能重新調(diào)整一個(gè)已經(jīng)存在的方法也是得益與C語(yǔ)言的類型系統(tǒng)(這個(gè)有點(diǎn)像一個(gè)變量能夠隱藏它的隱藏功能)
前綴
在Objective-C應(yīng)用中的所有類名都必須是全局唯一的。由于很多不同的框架中會(huì)有一些相似的功能,所以在名字上也可能會(huì)有重復(fù)(users, views, requests / responses 等等),所以蘋果官方文檔規(guī)定類名需要有2-3個(gè)字母作為前綴。
類前綴
蘋果官方建議兩個(gè)字母作為前綴的類名是為官方的庫(kù)和框架準(zhǔn)備的,而對(duì)于作為第三方開發(fā)者的我們,官方建議使用3個(gè)或者更多的字母作為前綴去命名我們的類。
一個(gè)資深的Mac或iOS開發(fā)者可能會(huì)記得下面大部分的縮寫標(biāo)識(shí)符:
Prefix | Frameworks |
---|---|
AB | AddressBook / AddressBookUI |
AC | Accounts |
AD | iAd |
AL | AssetsLibrary |
AU | AudioUnit |
AV | AVFoundation |
CA | CoreAnimation |
CB | CoreBluetooth |
CF | CoreFoundation / CFNetwork |
CG | CoreGraphics / QuartzCore / ImageIO |
CI | CoreImage |
CL | CoreLocation |
CM | CoreMedia / CoreMotion |
CV | CoreVideo |
EA | ExternalAccessory |
EK | EventKit / EventKitUI |
GC | GameController |
GLK | GLKit |
JS | JavaScriptCore |
MA | MediaAccessibility |
MC | MultipeerConnectivity |
MF | MessageUI |
MIDI | CoreMIDI |
MK | MapKit |
MP | MediaPlayer |
NK | NewsstandKit |
NS | Foundation, AppKit, CoreData |
PK | PassKit |
QL | QuickLook |
SC | SystemConfiguration |
Se | Security |
SK | StoreKit / SpriteKit |
SL | Social |
SS | Safari Services |
TW | |
UI | UIKit |
UT | MobileCoreServices |
第三方類前綴
直到最近,由于CocoaPods的出現(xiàn)和大量新的iOS開發(fā)者的涌現(xiàn),開源代碼的遍布,第三方代碼在很大程度上對(duì)蘋果和其余的Objective-C開發(fā)社區(qū)來(lái)說(shuō)已經(jīng)不是問(wèn)題了。最近蘋果官方的命名指南也發(fā)生了變化,它將三個(gè)字母作為前綴的建議只是做為一個(gè)習(xí)慣做法。
正因?yàn)檫@樣,那些已經(jīng)存在的第三方庫(kù)依然使用2個(gè)字母作為前綴,你可以參考一些那些在GitHub上得到很多start的Objective-C的倉(cāng)庫(kù)。
Prefix | Frameworks |
---|---|
AF | AFNetworking (“Alamofire”) |
RK | RestKit |
PU | GPUImage |
SD | SDWebImage |
MB | MBProgressHUD |
FB | Facebook SDK |
FM | FMDB (“Flying Meat”) |
JK | JSONKit |
UI | FlatUI |
NI | Nimbus |
AC | Reactive Cocoa |
我們已經(jīng)看到這個(gè)第三方庫(kù)的前綴已經(jīng)和我的AFNetworking一樣了,所以最好還是要在你的代碼中遵守要三個(gè)字母以上的作為類前綴的規(guī)定(https://github.com/AshFurrow/AFTabledCollectionView)。
對(duì)于那些針對(duì)特殊功能而寫的第三方庫(kù)的作者,可以考慮在下一次主要升級(jí)時(shí)使用@compatibility_alias來(lái)為那些使用者們提供一個(gè)天衣無(wú)縫的轉(zhuǎn)移路徑。
方法前綴
不僅是類容易造成命名沖突,selectors也很容易造成命名沖突,甚至方法比類會(huì)有更多的問(wèn)題。
考慮一下這個(gè)category:
- @interface NSString (PigLatin)
- - (NSString *)pigLatinString;
- @end
如果 -pigLatinString方法被另一個(gè)category實(shí)現(xiàn)了(或者以后版本的iOS或者M(jìn)ac OS X 在NSString類中也添加了同樣名字的方法),那么調(diào)用這個(gè)方法就會(huì)得到未定義的行為錯(cuò)誤,因?yàn)槲覀儾荒鼙WC在runtime中哪個(gè)方法會(huì)先被定義。
我們可以通過(guò)在方法名前加前綴來(lái)避免這個(gè)問(wèn)題,就像加這個(gè)類名一樣(在類別名前加前綴也是個(gè)好辦法):
- @interface NSString (XXXPigLatin)
- - (NSString *)xxx_pigLatinString;
- @end
蘋果官方建議所有category方法使使用前綴,這個(gè)建議比類名需要加前綴的規(guī)定更加廣為人知和接受。
很多開發(fā)者都在熱情地討論著這個(gè)規(guī)定的某一方面。然而,無(wú)論是通過(guò)成本角度還是效益角度來(lái)衡量命名沖突風(fēng)險(xiǎn)的可能性都是是不全面的:
category的主要功能是通過(guò)語(yǔ)法糖將一些有用的功能包裹進(jìn)原來(lái)的類中。任何一個(gè)category方法都可以被選擇性實(shí)現(xiàn),你也可以把他當(dāng)做是self的一個(gè)隱型的功能方法。
當(dāng)我在編譯器的環(huán)境參數(shù)中將OBJC_PRINT_REPLACED_METHODS這個(gè)參數(shù)設(shè)置為YES,那我們就能在編譯的時(shí)候檢測(cè)方法名是否有沖突。實(shí)際上,方法名的沖突是很少發(fā)生的,而且在發(fā)生的時(shí)候,他們通常會(huì)得到一個(gè)needlessly duplicated across dependencies的提示。即使發(fā)生最壞的情況,程序在運(yùn)行是出現(xiàn)異常,那么很可能是兩個(gè)方法名一樣,那么他們做的事情也是一樣的,所以結(jié)果也不會(huì)有什么變化。就像Swiss Army Knife寫了一個(gè)category,他定義了NSArray中的-firstObject這個(gè)方法,那么只要蘋果官方?jīng)]有在NSArray中加這個(gè)方法的話,那么這個(gè)類別方法一直有效的。
在蘋果官方的編程指南有很多嚴(yán)肅又松散的解釋。這里沒(méi)有固定的文檔,他們可能一直變化。看到這里,如果你還是懸而未決,那么你只需要把的category方法名加上前綴,如果你還是選擇不去做任何改變,那么你就等著自食其果吧。
Swizzling
在Swizzling時(shí),方法名加前綴或者后綴也是非常有必要的,這個(gè)我在上周關(guān)于swizzling的文章中提到過(guò)。
- @implementation UIViewController (Swizzling)
- @implementation UIViewController (Swizzling)
- - (void)xxx_viewDidLoad {
- [self xxx_viewDidLoad];
- // Swizzled implementation
- }
我們真的需要命名空間么?
在最近關(guān)于Objective-C替換、改造和重塑的討論中,我可以明顯地發(fā)現(xiàn)命名空間是未來(lái)的一個(gè)趨勢(shì)。但是它到底給我們帶來(lái)了什么呢?
美學(xué)?除了IETE成員和軍事人員,我想沒(méi)有人會(huì)喜歡CLAs的視覺(jué)審美,但是用::,/或者另外的.這些符號(hào)真的能讓我們覺(jué)得更好么?你真的想要以后把NSArray叫做Foundation Array?(那我這個(gè)NSHipster.com這個(gè)博客不是也得改名字了?!)
語(yǔ)義學(xué)?我比較一下其他的語(yǔ)言,看看他們是怎么用命名空間的,那么你就會(huì)意識(shí)到命名空間不能解決所有不明確的問(wèn)題??赡茉谀承╊~外環(huán)境的情況下,那些命名空間會(huì)出現(xiàn)更多問(wèn)題。
你還是不贊同,那么你想象一下Objective-C的命名空間的實(shí)現(xiàn)可能會(huì)像這個(gè)樣子,你會(huì)覺(jué)得怎么樣:
- @namespaceX
- @implementation Obje
- @using F: Foundatio
- - (void)fo
- F:Array *array = @[<a href="http://www.jobbole.com/members/1/" rel="nofollow">@1</a>,@2, @3
- //
- @en
- @end
雖然Objective-C有繁瑣的代碼但也有容易理解的明顯優(yōu)點(diǎn)。我們作為開發(fā)者去討論NSString的時(shí)候,我們不會(huì)把它理解成別的意思,編 譯器也是一樣。當(dāng)我們?cè)陂喿x代碼時(shí),我們不需要過(guò)多地去考慮這些代碼是什么作用的。并且最重要的是,這個(gè)類名在google這些搜索引擎中很容易就可以找到。
不管怎樣,如果你對(duì)這個(gè)討論感興趣的話,我強(qiáng)烈建議你看一下Kyle Sluder的《 this namespace feature proposal 》。非常值得一看。