“為什麼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同名的靜態變數,編譯之後你會得到一個錯誤:
1 2 3 4 5 6 |
; html-script: false ] @interface XXObject : NSObject @end static char * XXObject;//將“XXObject”重新定義為不同的符號 |
也就是說,Objective-C的runtime在C語言的型別系統上又建立了一個抽象層,它甚至可以允許下面這段程式碼被編譯:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
; html-script: false ] @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:
1 2 3 4 5 |
; html-script: false ] @interface NSString (PigLatin) - (NSString *)pigLatinString; @end |
如果 -pigLatinString
方法被另一個category實現了(或者以後版本的iOS或者Mac OS X 在NSString類中也新增了同樣名字的方法),那麼呼叫這個方法就會得到未定義的行為錯誤,因為我們不能保證在runtime中哪個方法會先被定義。
我們可以通過在方法名前加字首來避免這個問題,就像加這個類名一樣(在類別名前加字首也是個好辦法):
1 2 3 4 |
; html-script: false ] @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的文章中提到過。
1 2 3 4 5 6 7 8 9 10 |
; html-script: false ] @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的名稱空間的實現可能會像這個樣子,你會覺得怎麼樣:
1 2 3 4 5 6 7 8 9 10 11 |
; html-script: false ] @namespaceX @implementation Obje @using F: Foundatio - (void)fo F:Array *array = @[@1,@2, @3 // @en @end |
雖然Objective-C有繁瑣的程式碼但也有容易理解的明顯優點。我們作為開發者去討論NSString的時候,我們不會把它理解成別的意思,編譯器也是一樣。當我們在閱讀程式碼時,我們不需要過多地去考慮這些程式碼是什麼作用的。並且最重要的是,這個類名在google這些搜尋引擎中很容易就可以找到。
不管怎樣,如果你對這個討論感興趣的話,我強烈建議你看一下Kyle Sluder的《 this namespace feature proposal 》。非常值得一看。