讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 10.2 類別與擴展 >

10.2 類別與擴展

類別是Objective-C的一個語言特性,你可以通過它探究現有的類並注入額外的方法。類別對應於Swift的擴展(參見第4章)。借助Swift擴展,你可以將類或實例方法添加到Cocoa類中;Swift頭文件大量使用了擴展,既用於組織Swift自己的對象類型,也用於修改Cocoa類。與之相同,Cocoa使用類別來組織自己的類。

Objective-C的類別有名字,你會在頭文件、文檔中看到對這些名字的引用。不過,名字本身是沒什麼意義的,因此不用考慮太多。

10.2.1  Swift如何使用擴展

查看主Swift頭文件,你會看到很多原生對像類型聲明都包含了一個初始聲明,後跟一系列擴展。比如,在聲明了泛型結構體Array<Element>後,Array結構體頭會繼續聲明不少於7個擴展。其中有些擴展增加了協議的使用;不過大多數並沒有。它們都會向Array添加屬性或方法聲明;這正是擴展的意義所在。

擴展的功能性不是最重要的;頭文件本可以在Array結構體的聲明中加入所有這些屬性與方法。相反,它將這些內容分散到了多個擴展中。擴展用於將相關的功能聚合到一起,組織對像類型的成員,從而使得開發者能夠更容易地理解。

在Swift Core Graphics頭文件中,一切都是擴展。Swift在這裡適配了其他地方定義的類型,將Swift數字類型用於Core Graphics和CGFloat數字類型,將Cocoa結構體如CGPoint與CGRect用作Swift對像類型。特別地,它向CGRect提供了多個附加屬性、初始化器與方法,這樣就可以直接按照Swift結構體與之交互,而不必調用Cocoa Core Graphics C輔助函數來操縱CGRect了。

10.2.2 你應該如何使用擴展

Swift允許編寫全局函數,這麼做也沒什麼錯的。不過,為了面向對象的封裝,你常常需要編寫作為已有對象類型一部分的函數。最簡單,也是最有效的方式就是通過擴展將這種函數作為方法注入已有的對象類型中。如果僅僅添加一兩個方法就使用子類化顯得太過於笨重了;此外,這麼做也沒什麼太大的好處。(另外,擴展可用於Swift全部3種對像類型,但我們卻無法子類化Swift枚舉和結構體。)

比如,假設想要向Cocoa的UIView類中添加一個方法。你可以子類化UIView並聲明自己的方法,不過這樣會使方法只位於你的UIView子類以及該子類的子類中:它不會出現在UIButton、UILabel及其他內建的UIView子類中,這是因為它們都是UIView的子類,而不是你定義的子類的子類,你也無法改變這一點!另外,擴展可以漂亮地解決這個問題:將方法注入到UIView中,那麼它會被所有內建的UIView子類所繼承。

在Swift 2.0中,你可以通過協議擴展以一種有選擇但卻統一的方式將功能注入類中。假設我需要一個UIButton和一個UIBarButtonItem(它們不是UIView,但卻擁有類似於按鈕的行為)來共享某個方法。我可以聲明一個協議,它擁有一個方法,同時在協議擴展中實現該方法,然後通過擴展讓UIButton和UIBarButtonItem使用該協議,因此就會擁有該方法:


protocol ButtonLike {
    func behaveInButtonLikeWay
}
extension ButtonLike {
    func behaveInButtonLikeWay {
        // ...
    }
}
extension UIButton : ButtonLike {}
extension UIBarButtonItem : ButtonLike {}
  

第4章介紹了幾個擴展示例,這些示例都來自於我所編寫的iOS程序(參見4.10節)。此外,我常常會與Swift頭文件相同的方式來使用擴展,將單個對象類型的代碼組織到多個擴展中,目的在於表述清晰。

10.2.3  Cocoa如何使用類別

Cocoa將類別作為一種組織工具,這一點與Swift擴展很相似。類的聲明常常會按照功能拆分為多個類別,這些類別位於單獨的頭文件中。

NSString就是個很好的示例。它定義在Foundation框架中,基本的方法聲明在NSString.h中。除了初始化器,我們發現NSString本身只有兩個方法,分別是length與characterAtIndex:,因為這兩個方法是字符串所需的最基礎的功能。

額外的NSString方法(創建字符串、處理字符串編碼、分割字符串、字符串搜索等)都分佈在各個類別當中。它們都位於Swift轉換後的擴展當中。比如,在String類本身的聲明之後,我們發現Swift的轉換中有如下代碼:


extension NSString {
    func substringFromIndex(from: Int) -> String
    func substringToIndex(to: Int) -> String
    // ...
}
  

這實際上是Swift對如下Objective-C代碼的轉換結果:


@interface NSString (NSStringExtensionMethods)
- (NSString *)substringFromIndex:(NSUInteger)from;
- (NSString *)substringToIndex:(NSUInteger)to;
// ...
  

符號(關鍵字@interface,後跟類名,再後跟一對圓括號,裡面是另一個名字)是Objective-C類別。

此外,雖然有一些Cocoa NSString類別出現在相同的文件NSString.h中,不過還有很多位於其他文件中。比如:

·字符串可能作為文件路徑名,因此我們在NSPathUtilities.h中發現了一個NSString類別,其方法和屬性如pathComponents等用於將路徑名字符串劃分為各個組成部分。

·在NSURL.h(主要用於聲明NSURL類及其類別)中還有另一個NSString類別,它聲明了用於處理URL字符串中百分號轉義的方法,如stringByAddingPercentEscapesUsingEncoding。

·在另一個完全不同的框架(UIKit)中,NSStringDrawing.h還添加了兩個NSString類別,其方法drawAtPoint:等用於在圖形上下文中繪製字符串。

這種組織方式對於程序員並沒有那麼重要,因為NSString就是個NSString,無論它如何獲得其方法都是如此。不過在查閱文檔時就很重要了!聲明在NSString.h、NSPathUtilities.h與NSURL.h中的NSString方法文檔都集中在NSString類文檔頁面中。不過,聲明在NSStringDrawing.h中的NSString方法卻不是這樣的;相反,它們位於單獨的文檔NSString UIKit Additions Reference中(推測一下,這是因為它們來自於不同的框架)。這樣,字符串繪製方法就會難以找到,特別是NSString類文檔頁面沒有指向其他文檔的鏈接。我認為這是Cocoa文檔結構的一個敗筆。