Objective-C 語言的命名空間詳解
“為什么Objecive-C中的很多類名都是NS開頭的呢?”
我保證在你第一次給別人介紹Objective-C的時候肯定會聽到這句話。
就像父母要向孩子解釋“什么是死亡”或者“圣誕老人是不存在的”問題一樣,父母總是寄希望時間會讓孩子自己找到答案。
你既然這么問了,實際上NS代表了NeXTSTEP (好吧,其實是代表NeXTSTEP/Sun,我們只是做個簡單的介紹),它被用于…
你越解釋,你會發現對方越失望,接下來,他們不在只是隨便問問了,他們開始問一些你更難解釋的問題–在Objective-C中@是什么?
命名一直是Objective-C的硬傷,和那些優雅的語言相比,Objective-C缺乏標識符容器這點引來了很多不切實際的批評家。
他們總是說:Objective-C不像其他流行語言一樣提供模塊化機制來避免類名和方法名的沖突。
相反地,Objective-C 依靠前綴來確保APP中的一些地方的方法名不會影響其他的地方有相同名字的代碼。
插入一個關于類型系統的題外話之后我們會繼續進入關于命名的討論。
C和Objective-C中的類型
我曾在這博客上多次提過,Objective-C是直接建立在C語言之上的,一個重要的原因是Objective-C和C語言共用一個類型系統,他們都要求標識符是全局唯一的。
你可以自己定義一個和@interface同名的靜態變量,編譯之后你會得到一個錯誤:
- @interface XXObject : NSObject
- @end
- static char * XXObject;//將“XXObject”重新定義為不同的符號
也就是說,Objective-C的runtime在C語言的類型系統上又創建了一個抽象層,它甚至可以允許下面這段代碼被編譯:
- @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
通過Objective-C的環境,程序能區別所有相同名字的類,協議,類別,實例變量,實例方法和類方法。
一個變量能重新調整一個已經存在的方法也是得益與C語言的類型系統(這個有點像一個變量能夠隱藏它的隱藏功能)
前綴
在Objective-C應用中的所有類名都必須是全局唯一的。由于很多不同的框架中會有一些相似的功能,所以在名字上也可能會有重復(users, views, requests / responses 等等),所以蘋果官方文檔規定類名需要有2-3個字母作為前綴。
類前綴
蘋果官方建議兩個字母作為前綴的類名是為官方的庫和框架準備的,而對于作為第三方開發者的我們,官方建議使用3個或者更多的字母作為前綴去命名我們的類。
一個資深的Mac或iOS開發者可能會記得下面大部分的縮寫標識符:
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的出現和大量新的iOS開發者的涌現,開源代碼的遍布,第三方代碼在很大程度上對蘋果和其余的Objective-C開發社區來說已經不是問題了。最近蘋果官方的命名指南也發生了變化,它將三個字母作為前綴的建議只是做為一個習慣做法。
正因為這樣,那些已經存在的第三方庫依然使用2個字母作為前綴,你可以參考一些那些在GitHub上得到很多start的Objective-C的倉庫。
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 |
我們已經看到這個第三方庫的前綴已經和我的AFNetworking一樣了,所以最好還是要在你的代碼中遵守要三個字母以上的作為類前綴的規定(https://github.com/AshFurrow/AFTabledCollectionView)。
對于那些針對特殊功能而寫的第三方庫的作者,可以考慮在下一次主要升級時使用@compatibility_alias來為那些使用者們提供一個天衣無縫的轉移路徑。
方法前綴
不僅是類容易造成命名沖突,selectors也很容易造成命名沖突,甚至方法比類會有更多的問題。
考慮一下這個category:
- @interface NSString (PigLatin)
- - (NSString *)pigLatinString;
- @end
如果 -pigLatinString方法被另一個category實現了(或者以后版本的iOS或者Mac OS X 在NSString類中也添加了同樣名字的方法),那么調用這個方法就會得到未定義的行為錯誤,因為我們不能保證在runtime中哪個方法會先被定義。
我們可以通過在方法名前加前綴來避免這個問題,就像加這個類名一樣(在類別名前加前綴也是個好辦法):
- @interface NSString (XXXPigLatin)
- - (NSString *)xxx_pigLatinString;
- @end
蘋果官方建議所有category方法使使用前綴,這個建議比類名需要加前綴的規定更加廣為人知和接受。
很多開發者都在熱情地討論著這個規定的某一方面。然而,無論是通過成本角度還是效益角度來衡量命名沖突風險的可能性都是是不全面的:
category的主要功能是通過語法糖將一些有用的功能包裹進原來的類中。任何一個category方法都可以被選擇性實現,你也可以把他當做是self的一個隱型的功能方法。
當我在編譯器的環境參數中將OBJC_PRINT_REPLACED_METHODS這個參數設置為YES,那我們就能在編譯的時候檢測方法名是否有沖突。實際上,方法名的沖突是很少發生的,而且在發生的時候,他們通常會得到一個needlessly duplicated across dependencies的提示。即使發生最壞的情況,程序在運行是出現異常,那么很可能是兩個方法名一樣,那么他們做的事情也是一樣的,所以結果也不會有什么變化。就像Swiss Army Knife寫了一個category,他定義了NSArray中的-firstObject這個方法,那么只要蘋果官方沒有在NSArray中加這個方法的話,那么這個類別方法一直有效的。
在蘋果官方的編程指南有很多嚴肅又松散的解釋。這里沒有固定的文檔,他們可能一直變化。看到這里,如果你還是懸而未決,那么你只需要把的category方法名加上前綴,如果你還是選擇不去做任何改變,那么你就等著自食其果吧。
Swizzling
在Swizzling時,方法名加前綴或者后綴也是非常有必要的,這個我在上周關于swizzling的文章中提到過。
- @implementation UIViewController (Swizzling)
- @implementation UIViewController (Swizzling)
- - (void)xxx_viewDidLoad {
- [self xxx_viewDidLoad];
- // Swizzled implementation
- }
我們真的需要命名空間么?
在最近關于Objective-C替換、改造和重塑的討論中,我可以明顯地發現命名空間是未來的一個趨勢。但是它到底給我們帶來了什么呢?
美學?除了IETE成員和軍事人員,我想沒有人會喜歡CLAs的視覺審美,但是用::,/或者另外的.這些符號真的能讓我們覺得更好么?你真的想要以后把NSArray叫做Foundation Array?(那我這個NSHipster.com這個博客不是也得改名字了?!)
語義學?我比較一下其他的語言,看看他們是怎么用命名空間的,那么你就會意識到命名空間不能解決所有不明確的問題。可能在某些額外環境的情況下,那些命名空間會出現更多問題。
你還是不贊同,那么你想象一下Objective-C的命名空間的實現可能會像這個樣子,你會覺得怎么樣:
- @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有繁瑣的代碼但也有容易理解的明顯優點。我們作為開發者去討論NSString的時候,我們不會把它理解成別的意思,編 譯器也是一樣。當我們在閱讀代碼時,我們不需要過多地去考慮這些代碼是什么作用的。并且最重要的是,這個類名在google這些搜索引擎中很容易就可以找到。
不管怎樣,如果你對這個討論感興趣的話,我強烈建議你看一下Kyle Sluder的《 this namespace feature proposal 》。非常值得一看。