先來看看如果沒有 Generic Type,程式寫起來有多累人。下面範例中兩個 Function 功能相同,只是一個針對Int
、一個針對String
,如果將來還出現其他型別的應用,複製貼上的方式實在不夠聰明啊!
func swapTwoInts(a: Int, b: Int) -> (Int, Int) {
return (b, a)
}
swapTwoInts(1, 2) //return: (.0 2, .1 1)
func swapTwoStrings(a: String, b: String) -> (String, String) {
return (b, a)
}
swapTwoStrings("foo", "bar") //return: (.0 "bar", .1 "foo")
改成 Generic Function 不但型別通吃,更能避免複製貼上衍生的問題。
<T>
宣告Generic Type用法。T
。func swapTwoValues<T>(a: T, b: T) -> (T, T) {
return (b, a)
}
swapTwoValues(1, 2) //return: (.0 2, .1 1)
swapTwoValues(3.14, 2.71) //return: (.0 2.71, .1 3.14)
swapTwoValues("foo", "bar") //return: (.0 "bar", .1 "foo")
雖然 Generic 很強大,BUT! 有時候卻沒那麼神奇。下面範例試圖將compareInt()
與compareString()
改造成通用型的compare()
,編譯器卻發出錯誤訊息。
func compareInt(a: Int, b: Int) -> Bool {
return a > b
}
func compareString(a: String, b: String) -> Bool {
return a > b
}
func compare<T>(a: T, b: T) -> Bool {
return a > b //error: error: binary operator '>' cannot be applied to two T
}
型別要能夠相互比較,必須繼承Comparable
的協定。
func compare<T: Comparable>(a: T, b: T) -> Bool {
return a > b
}
compare(1, 2) //return: false
compare(3.14, 2.71) //return: true
compare("foo", "bar") //return: true
看起來只要繼承特定協定就能符合 Generic 的要求,但是下面範例編譯器又抱怨Person
不支援Comparable
。
func compare<T: Comparable>(a: T, b: T) -> Bool {
return a > b
}
class Person {
var age: Int
init(age: Int) {
self.age = age
}
}
compare(Person(age: 35), Person(age: 20)) //error: error: cannot invoke 'compare' with an argument list of type '(Person, Person)'
沒有Comparable
功能就來實作吧,只要補上能夠對Person
做比較的運算子即可。
extension
擴充Person
的有關Comparable
功能。==(a: Person, b: Person)
。<(a: Person, b: Person)
。==
與<
,就能延伸出其他比較運算子>
、<=
、>=
。func compare<T: Comparable>(a: T, b: T) -> Bool {
return a > b
}
class Person {
var age: Int
init(age: Int) {
self.age = age
}
}
extension Person: Comparable {}
func ==(a: Person, b: Person) -> Bool {
return a.age == b.age
}
func <(a: Person, b: Person) -> Bool {
return a.age < b.age
}
var p1 = Person(age: 10)
var p2 = Person(age: 20)
compare(p1, p2)
能夠比較Person
好像沒什麼,但當我們想排序陣列,Comparable
就幫上很大的忙,讓程式簡潔又邏輯清楚,是不是很神奇呢?
var numbers = [1, 3, 2, 4]
numbers.sort(<) //return: [1, 2, 3, 4]
var persons = [Person(age: 15), Person(age: 44), Person(age: 37)]
persons.sort(<) //return: [{age 15}, {age 37}, {age 44}]
再看一次沒有 Generic Type 的世界。
struct IntQueue {
var items = [Int]()
mutating func enqueue(item: Int) {
items.append(item)
}
mutating func dequeue() -> Int? {
return items.count > 0 ? items.removeAtIndex(0) : nil
}
}
var iq = IntQueue()
iq.enqueue(1) //return: {[1]}
iq.enqueue(2) //return: {[1, 2]}
iq.enqueue(3) //return: {[1, 2, 3]}
iq.dequeue() //return: 1
iq.dequeue() //return: 2
iq.dequeue() //retrun: 3
iq.dequeue() //return: nil
struct StringQueue {
var items = [String]()
mutating func enqueue(item: String) {
items.append(item)
}
mutating func dequeue() -> String? {
return items.count > 0 ? items.removeAtIndex(0) : nil
}
}
var sq = StringQueue()
sq.enqueue("foo") //return: {["foo"]}
sq.enqueue("bar") //return: {["foo", "bar"]}
sq.enqueue("qiz") //return: {["foo", "bar", "qiz"]}
sq.dequeue() //return: "foo"
sq.dequeue() //return: "bar"
sq.dequeue() //return: "qiz"
sq.dequeue() //return: nil
套用 Generic Type 的struct
人生變成彩色的。
struct Queue<T> {
var items = [T]()
mutating func enqueue(item: T) {
items.append(item)
}
mutating func dequeue() -> T? {
return items.count > 0 ? items.removeAtIndex(0) : nil
}
}
var iq = Queue<Int>()
iq.enqueue(1) //return: {[1]}
iq.enqueue(2) //return: {[1, 2]}
iq.enqueue(3) //return: {[1, 2, 3]}
iq.dequeue() //return: 1
iq.dequeue() //return: 2
iq.dequeue() //retrun: 3
iq.dequeue() //return: nil
var sq = Queue<String>()
sq.enqueue("foo") //return: {["foo"]}
sq.enqueue("bar") //return: {["foo", "bar"]}
sq.enqueue("qiz") //return: {["foo", "bar", "qiz"]}
sq.dequeue() //return: "foo"
sq.dequeue() //return: "bar"
sq.dequeue() //return: "qiz"
sq.dequeue() //return: nil
定義 Function 接受特定類型的物件,並可搭配where
額外限目標制物件必須符合哪些protocol
。
eat<T: Stuff where T: Eatable>(food: T)
要求食物要是個東西Stuff
,不管好吃Delicious
或難吃Disgusting
只要能吃Eatable
就可以。Stone
沒有繼承Eatable
,呼叫eat()
會發現東西啃不下去。
protocol Eatable {}
protocol Delicious {}
protocol Disgusting {}
class Stuff {}
class Stone: Stuff {}
class Food: Stuff, Eatable {}
class DeliciousFood: Food, Delicious {}
class DisgustingFood: Food, Disgusting {}
func eat<T: Stuff where T: Eatable>(food: T) {}
eat(Food())
eat(DeliciousFood())
eat(DisgustingFood())
eat(Stone()) //error: cannot invoke 'eat' with an argument list of type '(Stone)'
Generic Class 實作方式與 Struct 大同小異,就差在繼承機制而已。
下面範例,雖然ColorBall
繼承與實作了Colorful
協定,但因為Queue<T: Box where T: Colorful>
只接受Box
物件,所以ColorBall
就被擋在門口囉。
protocol Colorful {
var color: String { get }
}
class Box {}
class ColorBox: Box, Colorful {
var color: String
init(color: String) {
self.color = color
}
}
class Ball {}
class ColorBall: Ball, Colorful {
var color: String
init(color: String) {
self.color = color
}
}
struct Queue<T: Box where T: Colorful> {
var items = [T]()
mutating func enqueue(item: T) {
items.append(item)
}
mutating func dequeue() -> T? {
return items.count > 0 ? items.removeAtIndex(0) : nil
}
func countColorItems(color: String) -> Int {
return items.reduce(0) { $1.color == color ? $0 + 1 : $0 }
}
}
var queue = Queue<ColorBox>()
queue.enqueue(ColorBox(color: "red"))
queue.enqueue(ColorBox(color: "blue"))
queue.enqueue(ColorBox(color: "red"))
queue.enqueue(ColorBox(color: "blue"))
queue.countColorItems("red") //return: 2
queue.enqueue(ColorBall(color: "yellow")) // error: cannot invoke 'enqueue' with an argument list of type '(ColorBall)'
reduce()
神奇用法請參考連結。