這兩章比較簡單, 以類型轉換這一章更為重要和常見.
類型轉換(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 "?"
這個特性可能對代碼的聚合比較好吧, 但是把所有東西都堆在一起可能也不好, 主要還是看體量. 聚合類型差不多就結束了, 我也沒有細看文檔, 只看了一下代碼, 然后嘗試了幾個寫法(例如值類型能不能聚合引用類型, 經測試是可以的), 細節還是查看官方文檔走