Swift學習筆記(十)--類型轉換和聚合類型

這兩章比較簡單, 以類型轉換這一章更為重要和常見.

類型轉換(Type Casting)

秉著沒有最簡潔只有更簡潔的理念, 蘋果在Swift里引入了兩個關鍵字來進行類型轉換相關的操作, 分別是is和as. 從名字上來看, 前者為是, 后者為當作, 可以稍稍看出其作用的區別. is可以對應為NSObject的isKindOfClass:方法, 但是isMemberOfClass:則沒有對應起來.

但是, 其實蘋果還是給我們留了后路的, Swift里面的AnyObject類型(其它類型不行)還是有isKindOfClass和isMemberOfClass方法的, 只是從這個態勢上來看, 蘋果其實不太希望我們做這么細的檢查, 一般需要這么做的話, 我們的寫法其實是可以優化的. 另外還需要提一下則是isMemberOfClass這2個方法需要傳入一個類, 可以這么玩, 下面的代碼摘抄自王巍的blog:

class ClassA { }
class ClassB: ClassA { }

let obj1: AnyObject = ClassB()
let obj2: AnyObject = ClassB()

obj1.isKindOfClass(ClassA.self)    // true
obj2.isMemberOfClass(ClassA.self)  // false

除了類型的檢查, 后面還有檢查類型是否實現了某個協議, 這個講到協議再說.

首先, 先把官方定義的幾個類引入進來:

// 基類
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")
]
// library會被推導為[MediaItem]

之后的幾個小節會圍繞這3個類進行類型轉換的講解.

檢查類型(Checking Type)

類型用is操作符來檢查, 如果返回為true則代表成立, 否則不成立. 使用起來是這樣的:

var movieCount = 0
var songCount = 0

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

向下轉換(DownCasting)

所謂向下轉換是指父類對象轉換為子類對象. Swift里面用as?或as!操作符來進行這種轉換操作. 這種操作是有可能失敗的, 如上面library[0]是Movie類型的, 但是library的類型是[MediaItem], 存進數組是按MediaItem來存的, 但是要取出來操作就勢必要把MediaItem向下轉換為Movie了.

當然, 這種做法是可能會失敗的, 如library[1]是Song類型的, 如果用as轉換為Movie就會失敗. 上面說過, 轉換有兩種形式, 一種是用as?的條件形式, 將會返回一個目標類型的可選類型值, 也就是說, 成功返回目標類型的對象, 失敗就返回nil. 另一種是as!的轉換+強制拆包的形式(和之前講optional的?和!是一樣的概念), 這種比較危險的做法稍后會講.

上面說到轉換是會失敗的, 返回nil, 所以標準的寫法就又要用到if let了, 看下面的例子:

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的類型轉換

ObjC里面不指定特定類型的時候用id, 在Swift里面引入了兩個特殊的類型, 就是AnyObject和Any, 它們的區別如下:
1). AnyObject可以表示任意類類型的實例
2). Any可以表示任意類型的實例, 包括函數類型(說明函數在Swift里面是一等公民)

注意, 這倆特殊類型雖然有時候很有用, 但是萬萬確保在確實需要的時候才用, 一般來說最好是能夠在代碼中用自己期望的類型. (因為這樣可以避免很多類型檢查很轉化, 把代碼變得復雜)

AnyObject

[AnyObject]在Cocoa API里面是很常見的, 這是很正常的, 畢竟底層的API必須要兼容上層的業務代碼. 雖說數組里面是AnyObject, 但我們大多數情況都是很確定數組里面的類型是什么的, 比如:

let someObjects: [AnyObject] = [  // 如果靠類型推導則是[Movie]類型
    Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
    Movie(name: "Moon", director: "Duncan Jones"),
    Movie(name: "Alien", director: "Ridley Scott")
]

這個時候someObjects里面的元素肯定都是Movie, 所以類型轉換的時候可以用as!, 如:

for object in someObjects {
    let movie = object as! Movie   // 不需要if let, 因為!是自動拆包的
    print("Movie: '\(movie.name)', dir. \(movie.director)")
}
// 秉著只有更簡潔的精神, 蘋果簡化如下:
for movie in someObjects as! [Movie] {  // 加中括號是因為這個轉換操作是對someObjects做的
    print("Movie: '\(movie.name)', dir. \(movie.director)")
}
Any

Any可以往里面塞各種類型的東西, 直接看官網的例子:

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

// 這么多類型, 要拆包就用switch語句比較合適了:
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")
    }
}

所以用Any還是比較麻煩的, 不到萬不得已, 還是不要輕易使用, 想想別的解決辦法可能會更簡潔, 更好維護. 類型轉換差不多到這, 具體細節參考官方文檔

聚合類型

所謂聚合類型就是在某個類型里面再定義一個類型, 定義在內部的類型外面是無法直接使用的, 需要通過外部的類型間接使用. 看看官網的例子基本上就應該不會有太多問題了.

struct BlackjackCard {
    
    // nested Suit enumeration
    enum Suit: Character {
        case Spades = "?", Hearts = "?", Diamonds = "?", Clubs = "?"
    }
    
    // nested Rank enumeration
    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 properties and methods
    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: \(theAceOfSpades.description)")
// prints "theAceOfSpades: suit is ?, value is 1 or 11"
// 間接使用內部類型
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
// heartsSymbol is "?"

這個特性可能對代碼的聚合比較好吧, 但是把所有東西都堆在一起可能也不好, 主要還是看體量. 聚合類型差不多就結束了, 我也沒有細看文檔, 只看了一下代碼, 然后嘗試了幾個寫法(例如值類型能不能聚合引用類型, 經測試是可以的), 細節還是查看官方文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容