Swift Tour Learn (十) -- Swift 語法(類型轉化、嵌套類型、擴展)

本章將會介紹

定義一個類層次作為例子
檢查類型
向下轉型
Any 和 AnyObject 的類型轉換
嵌套類型實踐
引用嵌套類型
擴展語法
計算型屬性
構造器
方法
下標
嵌套類型

類型轉化

類型轉換 可以判斷實例的類型,也可以將實例看做是其父類或者子類的實例。

類型轉換在 Swift 中使用 is 和 as 操作符實現。這兩個操作符提供了一種簡單達意的方式去檢查值的類型或者轉換它的類型。

你也可以用它來檢查一個類型是否實現了某個協議。

1.定義一個類層次作為例子

你可以將類型轉換用在類和子類的層次結構上,檢查特定類實例的類型并且轉換這個類實例的類型成為這個層次結構中的其他類型。下面的三個代碼段定義了一個類層次和一個包含了這些類實例的數組,作為類型轉換的例子。

第一個代碼片段定義了一個新的基類 MediaItem。這個類為任何出現在數字媒體庫的媒體項提供基礎功能。特別的,它聲明了一個 String 類型的 name 屬性,和一個 init(name:) 初始化器。(假定所有的媒體項都有個名稱。)

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

下一個代碼段定義了 MediaItem 的兩個子類。第一個子類 Movie 封裝了與電影相關的額外信息,在父類(或者說基類)的基礎上增加了一個 director(導演)屬性,和相應的初始化器。第二個子類 Song,在父類的基礎上增加了一個 artist(藝術家)屬性,和相應的初始化器:

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

最后一個代碼段創建了一個數組常量 library,包含兩個 Movie 實例和三個 Song 實例。library 的類型是在它被初始化時根據它數組中所包含的內容推斷來的。Swift 的類型檢測器能夠推斷出 Movie 和 Song 有共同的父類 MediaItem,所以它推斷出 [MediaItem] 類作為 library 的類型:

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// 數組 library 的類型被推斷為 [MediaItem]

在幕后 library 里存儲的媒體項依然是 Movie 和 Song 類型的。但是,若你迭代它,依次取出的實例會是 MediaItem 類型的,而不是 Movie 和 Song 類型。為了讓它們作為原本的類型工作,你需要檢查它們的類型或者向下轉換它們到其它類型,就像下面描述的一樣。

2.檢查類型

用類型檢查操作符(is)來檢查一個實例是否屬于特定子類型。若實例屬于那個子類型,類型檢查操作符返回 true,否則返回 false。

下面的例子定義了兩個變量,movieCount 和 songCount,用來計算數組 library 中 Movie 和 Song 類型的實例數量:

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 “Media library contains 2 movies and 3 songs”

示例迭代了數組 library 中的所有項。每一次,for-in 循環設置 item 為數組中的下一個 MediaItem。

若當前 MediaItem 是一個 Movie 類型的實例,item is Movie 返回 true,否則返回 false。同樣的,item is Song 檢查 item 是否為 Song 類型的實例。在循環結束后,movieCount 和 songCount 的值就是被找到的屬于各自類型的實例的數量。

3.向下轉型

某類型的一個常量或變量可能在幕后實際上屬于一個子類。當確定是這種情況時,你可以嘗試向下轉到它的子類型,用類型轉換操作符(as? 或 as!)。

因為向下轉型可能會失敗,類型轉型操作符帶有兩種不同形式。條件形式as? 返回一個你試圖向下轉成的類型的可選值。強制形式 as! 把試圖向下轉型和強制解包(轉換結果結合為一個操作。

當你不確定向下轉型可以成功時,用類型轉換的條件形式(as?)。條件形式的類型轉換總是返回一個可選值,并且若下轉是不可能的,可選值將是 nil。這使你能夠檢查向下轉型是否成功。

只有你可以確定向下轉型一定會成功時,才使用強制形式(as!)。當你試圖向下轉型為一個不正確的類型時,強制形式的類型轉換會觸發一個運行時錯誤。

下面的例子,迭代了 library 里的每一個 MediaItem,并打印出適當的描述。要這樣做,item 需要真正作為 Movie 或 Song 的類型來使用,而不僅僅是作為 MediaItem。為了能夠在描述中使用 Movie 或 Song 的 director 或 artist 屬性,這是必要的。

在這個示例中,數組中的每一個 item 可能是 Movie 或 Song。事前你不知道每個 item 的真實類型,所以這里使用條件形式的類型轉換(as?)去檢查循環里的每次下轉:

for item in library {
    if let movie = item as? Movie {
        print("Movie: '\(movie.name)', dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: '\(song.name)', by \(song.artist)")
    }
}

// Movie: 'Casablanca', dir. Michael Curtiz
// Song: 'Blue Suede Shoes', by Elvis Presley
// Movie: 'Citizen Kane', dir. Orson Welles
// Song: 'The One And Only', by Chesney Hawkes
// Song: 'Never Gonna Give You Up', by Rick Astley

示例首先試圖將 item 下轉為 Movie。因為 item 是一個 MediaItem 類型的實例,它可能是一個 Movie;同樣,它也可能是一個 Song,或者僅僅是基類 MediaItem。因為不確定,as? 形式在試圖下轉時將返回一個可選值。item as? Movie 的返回值是 Movie? 或者說“可選 Movie”。

當向下轉型為 Movie 應用在兩個 Song 實例時將會失敗。為了處理這種情況,上面的例子使用了可選綁定(optional binding)來檢查可選 Movie 真的包含一個值(這個是為了判斷下轉是否成功。)可選綁定是這樣寫的“if let movie = item as? Movie”,可以這樣解讀:

“嘗試將 item 轉為 Movie 類型。若成功,設置一個新的臨時常量 movie 來存儲返回的可選 Movie 中的值”

若向下轉型成功,然后 movie 的屬性將用于打印一個 Movie 實例的描述,包括它的導演的名字 director。相似的原理被用來檢測 Song 實例,當 Song 被找到時則打印它的描述(包含 artist 的名字)。

注意
轉換沒有真的改變實例或它的值。根本的實例保持不變;只是簡單地把它作為它被轉換成的類型來使用。

4.Any 和 AnyObject 的類型轉化

Swift 為不確定類型提供了兩種特殊的類型別名:

  • Any 可以表示任何類型,包括函數類型。
  • AnyObject 可以表示任何類類型的實例。

只有當你確實需要它們的行為和功能時才使用 Any 和 AnyObject。在你的代碼里使用你期望的明確類型總是更好的。

這里有個示例,使用 Any 類型來和混合的不同類型一起工作,包括函數類型和非類類型。它創建了一個可以存儲 Any 類型的數組 things:

var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

things 數組包含兩個 Int 值,兩個 Double 值,一個 String 值,一個元組 (Double, Double),一個Movie實例“Ghostbusters”,以及一個接受 String 值并返回另一個 String 值的閉包表達式。

你可以在 switch 表達式的 case 中使用 is 和 as 操作符來找出只知道是 Any 或 AnyObject 類型的常量或變量的具體類型。下面的示例迭代 things 數組中的每一項,并用 switch 語句查找每一項的類型。有幾個 switch 語句的 case 綁定它們匹配到的值到一個指定類型的常量,從而可以打印這些值:

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as a Double")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        print("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        print("a movie called '\(movie.name)', dir. \(movie.director)")
    case let stringConverter as (String) -> String:
        print(stringConverter("Michael"))
    default:
        print("something else")
    }
}

// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman
// Hello, Michael

注意
Any類型可以表示所有類型的值,包括可選類型。Swift 會在你用Any類型來表示一個可選值的時候,給你一個警告。如果你確實想使用Any類型來承載可選值,你可以使用as操作符顯式轉換為Any,如下所示:

let optionalNumber: Int? = 3
things.append(optionalNumber)        // 警告
things.append(optionalNumber as Any) // 沒有警告
5.類型轉化總結
類型轉換

// 定義一個類層次
class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]

// 檢查類型
var movieCount = 0
var songCount = 0
for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}
print("Media library contains \(movieCount) movies and \(songCount) songs")

// 向下轉型
for item in library {
    if let movie = item as? Movie {
        print("Movie: '\(movie.name)', dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: '\(song.name)', by \(song.artist)")
    }
}

// Any和AnyObject
// Any 表示任何類型,包括函數類型
// AnyObject 表示任何類類型的實例

var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "我不是潘金蓮", director: "馮小剛"))
things.append({ (name: String) -> String in "Hello, \(name)" })

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as an Int")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let(x, y) as (Double, Double):
        print("an (x, y) point at (\(x), \(y))")
    case let movie as Movie:
        print("a movie called '\(movie.name)', dir. \(movie.director)")
    case let stringConverter as (String) -> String:
        print(stringConverter("Michael"))
    default:
        print("sonething else")
    }
}

嵌套類型

枚舉常被用于為特定類或結構體實現某些功能。類似的,枚舉可以方便的定義工具類或結構體,從而為某個復雜的類型所使用。為了實現這種功能,Swift 允許你定義嵌套類型,可以在支持的類型中定義嵌套的枚舉、類和結構體。

要在一個類型中嵌套另一個類型,將嵌套類型的定義寫在其外部類型的{}內,而且可以根據需要定義多級嵌套。

1.嵌套類型實踐

下面這個例子定義了一個結構體BlackjackCard(二十一點),用來模擬BlackjackCard中的撲克牌點數。BlackjackCard結構體包含兩個嵌套定義的枚舉類型Suit和Rank。

在BlackjackCard中,Ace牌可以表示1或者11,Ace牌的這一特征通過一個嵌套在Rank枚舉中的結構體Values來表示:

struct BlackjackCard {
    // 嵌套的 Suit 枚舉
    enum Suit: Character {
       case Spades = "?", Hearts = "?", Diamonds = "?", Clubs = "?"
    }

    // 嵌套的 Rank 枚舉
    enum Rank: Int {
       case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
       case Jack, Queen, King, Ace
       struct Values {
           let first: Int, second: Int?
       }
       var values: Values {
        switch self {
        case .Ace:
            return Values(first: 1, second: 11)
        case .Jack, .Queen, .King:
            return Values(first: 10, second: nil)
        default:
            return Values(first: self.rawValue, second: nil)
            }
       }
    }

    // BlackjackCard 的屬性和方法
    let rank: Rank, suit: Suit
    var description: String {
        var output = "suit is \(suit.rawValue),"
        output += " value is \(rank.values.first)"
        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    }
}

Suit枚舉用來描述撲克牌的四種花色,并用一個Character類型的原始值表示花色符號。

Rank枚舉用來描述撲克牌從Ace~10,以及J、Q、K,這13種牌,并用一個Int類型的原始值表示牌的面值。(這個Int類型的原始值未用于Ace、J、Q、K這4種牌。)

如上所述,Rank枚舉在內部定義了一個嵌套結構體Values。結構體Values中定義了兩個屬性,用于反映只有Ace有兩個數值,其余牌都只有一個數值:

  • first的類型為Int
  • second的類型為Int?,或者說“可選 Int”

Rank還定義了一個計算型屬性values,它將會返回一個Values結構體的實例。這個計算型屬性會根據牌的面值,用適當的數值去初始化Values實例。對于J、Q、K、Ace這四種牌,會使用特殊數值。對于數字面值的牌,使用枚舉實例的原始值。

BlackjackCard結構體擁有兩個屬性——rank與suit。它也同樣定義了一個計算型屬性description,description屬性用rank和suit中的內容來構建對撲克牌名字和數值的描述。該屬性使用可選綁定來檢查可選類型second是否有值,若有值,則在原有的描述中增加對second的描述。

因為BlackjackCard是一個沒有自定義構造器的結構體,在結構體的逐一成員構造器中可知,結構體有默認的成員構造器,所以你可以用默認的構造器去初始化新常量theAceOfSpades:

let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 打印 “theAceOfSpades: suit is ?, value is 1 or 11”

盡管Rank和Suit嵌套在BlackjackCard中,但它們的類型仍可從上下文中推斷出來,所以在初始化實例時能夠單獨通過成員名稱(.Ace和.Spades)引用枚舉實例。在上面的例子中,description屬性正確地反映了黑桃A牌具有1和11兩個值。

2.引用嵌套類型

在外部引用嵌套類型時,在嵌套類型的類型名前加上其外部類型的類型名作為前綴:

let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
// 紅心符號為 “?”

對于上面這個例子,這樣可以使Suit、Rank和Values的名字盡可能的短,因為它們的名字可以由定義它們的上下文來限定。

3.嵌套類型總結
嵌套類型

struct BlackjackCard {
    // 嵌套Suit枚舉
    enum Suit: Character {
        case Spades = "?", Hearts = "?", Diamonds = "?", Clubs = "?"
    }

    // 嵌套的Rank枚舉
    enum Rank: Int {
        case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
        case Jack, Queue, King, Ace
        struct Values {
            let first: Int, second: Int?
        }
        var values: Values {
            switch self {
            case .Ace:
                return Values(first: 1, second: 11)
            case .Jack, .Queue, .King:
                return Values(first: 10, second: nil)
            default:
                return Values(first: self.rawValue, second: nil)
            }
        }
    }

    // 定義屬性和方法
    let rank: Rank, suit: Suit
    var description: String {
        var output = "suit is \(suit.rawValue)"
        output += " value is \(rank.values.first)"
        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    }
}

let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
print(theAceOfSpades.description)

let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
print(heartsSymbol)

擴展

擴展 就是為一個已有的類、結構體、枚舉類型或者協議類型添加新功能。這包括在沒有權限獲取原始源代碼的情況下擴展類型的能力(即 逆向建模 )。擴展和 Objective-C 中的分類類似。(與 Objective-C 不同的是,Swift 的擴展沒有名字。)
Swift 中的擴展可以:

  • 添加計算型屬性和計算型類型屬性
  • 定義實例方法和類型方法
  • 提供新的構造器
  • 定義下標
  • 定義和使用新的嵌套類型
  • 使一個已有類型符合某個協議

在 Swift 中,你甚至可以對協議進行擴展,提供協議要求的實現,或者添加額外的功能,從而可以讓符合協議的類型擁有這些功能。

注意
擴展可以為一個類型添加新的功能,但是不能重寫已有的功能。

1.擴展語法

使用關鍵字 extension 來聲明擴展:

extension SomeType {
    // 為 SomeType 添加的新功能寫到這里
}

可以通過擴展來擴展一個已有類型,使其采納一個或多個協議。在這種情況下,無論是類還是結構體,協議名字的書寫方式完全一樣:

extension SomeType: SomeProtocol, AnotherProctocol {
    // 協議實現寫到這里
}

注意
如果你通過擴展為一個已有類型添加新功能,那么新功能對該類型的所有已有實例都是可用的,即使它們是在這個擴展定義之前創建的。

2.計算型屬性

擴展可以為已有類型添加計算型實例屬性和計算型類型屬性。下面的例子為 Swift 的內建 Double 類型添加了五個計算型實例屬性,從而提供與距離單位協作的基本支持:

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 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印 “One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印 “Three feet is 0.914399970739201 meters”

這些計算型屬性表達的含義是把一個 Double 值看作是某單位下的長度值。即使它們被實現為計算型屬性,但這些屬性的名字仍可緊接一個浮點型字面值,從而通過點語法來使用,并以此實現距離轉換。

在上述例子中,Double 值 1.0 用來表示“1米”。這就是為什么計算型屬性 m 返回 self,即表達式 1.m 被認為是計算 Double 值 1.0。

其它單位則需要一些單位換算。一千米等于 1,000 米,所以計算型屬性 km 要把值乘以 1_000.00 來實現千米到米的單位換算。類似地,一米有 3.28024 英尺,所以計算型屬性 ft 要把對應的 Double 值除以 3.28024 來實現英尺到米的單位換算。

這些屬性是只讀的計算型屬性,為了更簡潔,省略了 get 關鍵字。它們的返回值是 Double,而且可以用于所有接受 Double 值的數學計算中:

let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 打印 “A marathon is 42195.0 meters long”

注意
擴展可以添加新的計算型屬性,但是不可以添加存儲型屬性,也不可以為已有屬性添加屬性觀察器。

3.構造器

擴展可以為已有類型添加新的構造器。這可以讓你擴展其它類型,將你自己的定制類型作為其構造器參數,或者提供該類型的原始實現中未提供的額外初始化選項。

擴展能為類添加新的便利構造器,但是它們不能為類添加新的指定構造器或析構器。指定構造器和析構器必須總是由原始的類實現來提供。

注意
如果你使用擴展為一個值類型添加構造器,同時該值類型的原始實現中未定義任何定制的構造器且所有存儲屬性提供了默認值,那么我們就可以在擴展中的構造器里調用默認構造器和逐一成員構造器。
正如在值類型的構造器代理中描述的,如果你把定制的構造器寫在值類型的原始實現中,上述規則將不再適用。

下面的例子定義了一個用于描述幾何矩形的結構體 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 未提供定制的構造器,因此它會獲得一個逐一成員構造器。又因為它為所有存儲型屬性提供了默認值,它又會獲得一個默認構造器。這些構造器可以用于構造新的 Rect 實例:

let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))

你可以提供一個額外的接受指定中心點和大小的構造器來擴展 Rect 結構體:

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)
    }
}

這個新的構造器首先根據提供的 center 和 size 的值計算一個合適的原點。然后調用該結構體的逐一成員構造器 init(origin:size:),該構造器將新的原點和大小的值保存到了相應的屬性中:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))
// centerRect 的原點是 (2.5, 2.5),大小是 (3.0, 3.0)

注意
如果你使用擴展提供了一個新的構造器,你依舊有責任確保構造過程能夠讓實例完全初始化。

4.方法

擴展可以為已有類型添加新的實例方法和類型方法。下面的例子為 Int 類型添加了一個名為 repetitions 的實例方法:

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

這個 repetitions(task:) 方法接受一個 () -> Void 類型的單參數,表示沒有參數且沒有返回值的函數。

定義該擴展之后,你就可以對任意整數調用 repetitions(task:) 方法,將閉包中的任務執行整數對應的次數:

3.repetitions({
    print("Hello!")
})
// Hello!
// Hello!
// Hello!

可以使用尾隨閉包讓調用更加簡潔:

3.repetitions {
    print("Goodbye!")
}
// Goodbye!
// Goodbye!
// Goodbye!

可變實例方法

通過擴展添加的實例方法也可以修改該實例本身。結構體和枚舉類型中修改 self 或其屬性的方法必須將該實例方法標注為 mutating,正如來自原始實現的可變方法一樣。

下面的例子為 Swift 的 Int 類型添加了一個名為 square 的可變方法,用于計算原始值的平方值:

extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// someInt 的值現在是 9
5.下標

擴展可以為已有類型添加新下標。這個例子為 Swift 內建類型 Int 添加了一個整型下標。該下標 [n] 返回十進制數字從右向左數的第 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]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7

如果該 Int 值沒有足夠的位數,即下標越界,那么上述下標實現會返回 0,猶如在數字左邊自動補 0:

746381295[9]
// 返回 0,即等同于:
0746381295[9]
6.嵌套類型

擴展可以為已有的類、結構體和枚舉添加新的嵌套類型:

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
        }
    }
}

該例子為 Int 添加了嵌套枚舉。這個名為 Kind 的枚舉表示特定整數的類型。具體來說,就是表示整數是正數、零或者負數。

這個例子還為 Int 添加了一個計算型實例屬性,即 kind,用來根據整數返回適當的 Kind 枚舉成員。

現在,這個嵌套枚舉可以和任意 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])
// 打印 “+ + - 0 - 0 + ”

函數 printIntegerKinds(_:) 接受一個 Int 數組,然后對該數組進行迭代。在每次迭代過程中,對當前整數的計算型屬性 kind 的值進行評估,并打印出適當的描述。

注意
由于已知 number.kind 是 Int.Kind 類型,因此在 switch 語句中,Int.Kind 中的所有成員值都可以使用簡寫形式,例如使用 . Negative 而不是 Int.Kind.Negative。

7.擴展總結
擴展

// 使用extension添加擴展

// 計算型屬性
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 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters")

// 構造器的擴展
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()
}

let defaultRect = Rect()
let memberWiseRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))

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)
    }
}
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))

// 方法擴展
extension Int {
    func repetition(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}
3.repetition {
    print("Hello")
}

5.repetition(task: {
    print("Goodbye")
})

// 可變實例方法
extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
print(someInt)

// 下標
extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}
66678990[2]
78[4]

// 嵌套類型
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
        }
    }
}

func printIntegerKinds(_ numbers: [Int]) {
    for number in numbers {
        switch number.kind {
        case .Negative:
            print("-", terminator: " ")
        case .Positive:
            print("+", terminator: " ")
        case .Zero:
            print("0", terminator: " ")
        }
    }
}
printIntegerKinds([3, 19, -27, 0, -6, 9 ,7])
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容