在swift中,extension與Objective-C的category有點類似,但是extension比起category來說更加強大和靈活,它不僅可以擴充某種類型或結構體的方法,同時它還可以與protocol等結合使用,編寫出更加靈活和強大的代碼。 0. 概述 - extension summary
在swift中,swift可以為特定的class, strut, enum或者protocol添加新的特性。當你沒有許可權對原始碼進行改造的時候,此時可以通過extension來對類型進行擴充。extension有點類似於OC的類別 -- category,但稍微不同的是category有名字,而extension沒有名字。
swift的extension可以做如下幾件事, 添加計算屬性 - computed properties 添加方法 - methods 添加初始化方法 - initializers 添加附屬指令碼 - subscripts 添加並使用巢狀型別 - nested types 遵循並實現某一協議 - conform protocol
在swift中,你甚至可以對一個協議protocol進行擴充,實現其協議方法或添加額外的功能,以便於實現該協議的類型可以使用,在swift中,這叫做協議擴充 - protocol extension,後面的內容會舉例說明。
注意:extension可以為類型添加新的特性,但是它不能覆蓋已有的特性。例如Animal已經有eat的方法,我們不能使用extension覆蓋Animal的eat方法。 1. 文法 - extension syntax
定義extension的文法非常簡單,只需要使用extension關鍵字,如下代碼,
extension SomeType { // new functionality to add to SomeType goes here}
extension可以讓一個特定的類型實現一個或多個協議,也就是說無論對於class, structure或enum等類型而言,都可以實現一個或多個協議,如下代碼所示,
extension SomeType: SomeProtocol, AnotherProtocol { // implementation of protocol requirements goes here}
上面的代碼錶示了簡單的功能,即SomeType服從了SomeProtocol和AnotherProtocol協議,並實現兩個協議中的方法,我們可以對上面的代碼進行簡單的拆分,讓代碼更加簡潔易讀,如下代碼,
extension SomeType: SomeProtocol { // implentations of SomeProtocol}extension SomeType: AnotherProtocol { // implentations of AnotherProtocol}
2. 添加多種特性
在前面已經列舉了extension可以為類型添加諸多特性,下面逐一做簡單解釋,並舉例說明。 2.1 添加計算屬性 - computed properties
extension可以為已經存在的類型添加計算屬性(computed properties),下面的demo為swift內建的Double類型添加了5個計算屬性,分別是km, m, cm, mm, ft,用來提供基礎的計算距離的功能,如下代碼所示,
extension Double { var km: Double { return self * 1_000.0 } var m: Double { return self } var cm: Double { return self / 100.0 } var mm: Double { return self / 1_000.0 } var ft: Double { return self / 3.28084 }}// usage of Double extensionlet oneInch = 25.4.mmprint("One inch is \(oneInch) meters")// Prints "One inch is 0.0254 meters"let threeFeet = 3.ftprint("Three feet is \(threeFeet) meters")// Prints "Three feet is 0.914399970739201 meters"
什麼是計算屬性呢。這大概算是swift語言的特性吧,在swift中屬性有兩種類型,一種是儲存屬性,另一種是計算屬性。儲存屬性就是儲存在特定的class, struct中的一個常量或變數,可以在定義儲存屬性的時候指定預設值,也可以在構造過程中設定或修改儲存屬性的值,需要注意的是enum中並不能定義儲存屬性;而計算屬性不直接儲存值,而是提供一個getter, setter來間接擷取和設定其他屬性和變數的值。
歸納一下,swift中儲存屬性和計算屬性的區別如下表,
儲存屬性 |
計算屬性 |
儲存常量或變數屬性 |
用來計算數值,不是儲存數值 |
定義在class, struct中 |
定義在class, struct, enum中 |
在上面的demo中,這些計算屬性工作表示一個Double值應該被當做一個特定的長度單位,即千米、米、厘米、毫米等。雖然它們被當做計算屬性來實現,但可以將這些屬性名稱通過.操作符放在浮點值的後面。Double類型的浮點值1.0表示“一米”,這也是為什麼.m計算屬性返回了該Double值本身。同樣,1米約等於3.28084寸(feet),所以ft計算屬性轉換成"米"時候需要乘以3.28084。
這些屬性都是唯讀(read-only)的計算屬性,所以為了方便,在定義它們的時候只需要使用get關鍵字,不過為了簡潔和直觀,這個demo直接省略了get關鍵字。這些計算屬性的傳回值是Double類型,所以可以用在任何Double類型適用的地方進行數學計算,例如下面的代碼,
let aMarathon = 42.km + 195.mprint("A marathon is \(aMarathon) meters long")// Prints "A marathon is 42195.0 meters long"
注意:extension可以添加計算屬性,但是不能添加儲存屬性,也不能為當前屬性添加觀察者。 2.2 添加構造器 - initializers
extension可以為已經存在的類(class)添加便捷初始化方法。這樣你就可以對其他的類型進行擴充,以便該類型在初始化時可以接受你自訂的類型作為初始化的參數,這樣就為該類型添加了額外的初始化方法,在建立該類型的對象時提供了更多的選擇。
下面的例子定義了一個結構體類型Rect,它表示一個長方形幾何圖形;同樣我們定義了另外兩個結構體類型Size和Point,兩者的所有屬性都設定了0.0預設值。如下代碼所示,
struct Size { var width = 0.0, height = 0.0}struct Point { var x = 0.0, y = 0.0}struct Rect { var origin = Point() var size = Size()}
因為結構體Rect為它所有屬性都提供了預設值,所以它自動擁有一個預設初始化方法和一個成員逐一(memberwise)初始化方法,我們可以使用這兩個初始化方法來建立Rect結構體的執行個體,如下代碼,
let defaultRect = Rect()let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))
現在我們可以通過extension為結構體Rect添加一個額外的初始化方法,該初始化方法接受一個point和size作為參數,如下代碼,
extension Rect { init(center: Point, size: Size) { let originX = center.x - (size.width / 2) let originY = center.y - (size.height / 2) self.init(origin: Point(x: originX, y: originY), size: size) }}
這個自訂的初始化方法實現的邏輯並不複雜,首先它通過傳入的Point參數center和Size參數size計算出(originX, originY),然後調用該結構體的逐一成員初始化方法,即init(origin:size:),該初始化方法將origin和size參數儲存在對應的屬性中,如下代碼所示,
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
注意:如果你使用extension來添加初始化方法,你同樣需要保證該類型的初始化方法結束時,它的每一個屬性被完全的初始化了。 2.3.1 添加方法 - methods
跟OC的category類似,通過extension,可以為某一類型添加執行個體方法(instance method)和類型方法(type method),如下代碼為Int類型添加了一個名為repetitions的執行個體方法,
extension Int { func repetitions(task: () -> Void) { for _ in 0..<self { task() } }}
新增的repetitions(task:)方法接受一個閉包()->Void作為參數,該閉包作為一個函數,並且該函數沒有入參和傳回值。
如下代碼所示,我們來進行一個簡單的測試,
3.repetitions { print("Hello!")}// Hello!// Hello!// Hello!
2.3.2 添加突變方法 - mutating method
通過extension添加的執行個體方法同樣可以修改(modify)或突變(mutate)該執行個體本身,如果結構體和枚舉定義的方法想要改變自身或自身的屬性,那麼該執行個體方法必須被標記為突變(mutating)的。
下面的例子為Int類型添加了一個名為square的突變方法,它的作用是計算原始值的平方,如下代碼所示,
extension Int { mutating func square() { self = self * self }}var someInt = 3someInt.square()// someInt is now 9
這裡針對mutating關鍵字再囉嗦一句,如果我們把mutating關鍵字刪除,則編譯器會報錯,只有mutating修飾的方法才能更改執行個體屬性和執行個體本身,mutating關鍵字與extension, protocol結合使用,可以用更簡潔的代碼實現更複雜的功能。筆者建議讀者搜尋資料,寫寫demo,加深印象和理解。 2.4 添加附屬指令碼 - subscripts
extension可以為某一個特定類型添加附屬指令碼subscript。那麼什麼是附屬指令碼呢。附屬指令碼可以定義在class, struct, enum中,可以認為是訪問對象,集合或序列的捷徑,不需要在調用執行個體的特定的賦值方法和存取方法。舉例來說,用附屬指令碼訪問一個Array執行個體中的元素可以寫為someArray[index],訪問Dictionary執行個體中的元素可以寫為someDictionary[key],讀者可能已經注意到,通過這種捷徑對siwft中Array和Dictionary元素進行訪問我們經常使用,所以可以推斷,swift已經預設幫開發人員實現了附屬指令碼的特性。
下面的例子為Int類型添加了整數下標的附屬指令碼,該附屬指令碼[n]返回該數字對應的十進位(decimal)的第n位的數字,當然計數方式從最右側開始,例如,123456789[0]返回9,而123456789[1]返回8,該方法的具體實現如下代碼所示,
extension Int { subscript(digitIndex: Int) -> Int { var decimalBase = 1 for _ in 0..<digitIndex { decimalBase *= 10 } return (self / decimalBase) % 10 }}746381295[0]// returns 5746381295[1]// returns 9746381295[2]// returns 2746381295[8]// returns 7
仔細分析一下subscript方法,實現的邏輯其實就是小學生的練習題,這裡不做贅述。
如果一個Int數字沒有足夠多的位元,那麼它會在最左邊添加0來補全,然後返回0給調用方,如下代碼,
746381295[9]// returns 0, as if you had requested:0746381295[9]
2.5 添加巢狀型別 - nested types
extension可以為類(class)、結構體(structure)和枚舉(enumation)添加巢狀型別,如下代碼,
extension Int { enum Kind { case negative, zero, positive } var kind: Kind { switch self { case 0: return .zero case let x where x > 0: return .positive default: return .negative } }}
上面的demo為Int添加了嵌套的枚舉類型,這個枚舉名為Kind,用來表示一個數字是正數、複數還是0,之所以說是嵌套,是因為該枚舉定義在Int的extension內部。(這裡,我可能對嵌套的理解有誤,這段理解暫時保留,歡迎讀者指正。)
這個demo還為Int添加了一個計算屬性(computed property),名為kind,針對數位值不同,分別返回.zero, .positive或.negative。
現在該嵌套的枚舉類型可以在任意的Int值中使用,如下代碼所示,
func printIntegerKinds(_ numbers: [Int]) { for number in numbers { switch number.kind { case .negative: print("- ", terminator: "") case .zero: print("0 ", terminator: "") case .positive: print("+ ", terminator: "") } } print("")}printIntegerKinds([3, 19, -27, 0, -6, 0, 7])// Prints "+ + - 0 - 0 + "
上面的代碼簡單易懂,printIntegerKinds(_:)接受一個Int類型的數組,然後遍曆數組並判斷每個元素的kind來判斷數組元素的正、負還是0,代碼很簡單,不多做解釋。 3. 參考連結 apple documents - extensions siwft properties