第三章 - 泛型(Generic) 是什麼? 有什麼好處?
泛型代碼可以讓你寫出根據自我需求定義、適用於任何類型的,靈活且可重用的函數和類型。它的可以讓你避免重複的代碼,用一種清晰和抽像的方式來表達代碼的意圖。
你可能在C++中,聽到的術語是Template, 而Swift,Java和C#則採用Generic(泛型),但概念是相近的。
在Swift中,泛型可作用於幾個地方:
- Function
- Classess/Structs/Enums
- Protocol
- Extension
1. Function
先談談
在以往,我們可能會有幾個function,做很像的事情: 例如,交換兩個數字,交換兩個字串
//交換兩個數字
func swapInts(inout lhs: Int, inout _ rhs: Int){
//先把lhs存到temp
let temp = lhs
//把rhs賦值到lhs
lhs = rhs
//最後把temp賦值到rhs
rhs = temp
}
var a = 10
var b = 11
swapInts(&a, &b)
a //11
b //10
//交換兩個字串
func swapStrings(inout lhs: String, inout _ rhs: String){
//先把lhs存到temp
let temp = lhs
//把rhs賦值到lhs
lhs = rhs
//最後把temp賦值到rhs
rhs = temp
}
var aString = "Hello"
var bString = "World"
swap(&aString, &bString)
aString //World
bString //Hello
swapInts做的事,和swapStrings是不是很像呢?😏
所以這個時候,泛型就派上用場了 我們可以定義一個function叫swapValues,並透過泛型的Type來通吃對換這件事
a. 泛型Function的語法一:
func FName<Type>(a: Type, [Arguments...])->Void
代表泛型對象代表的Type 將透過傳入值的Type來動態決定
//一個通用於任何型別的swapValues的函式
func swapValues<Type>(inout lhs: Type, inout _ rhs: Type){
//先把lhs存到temp
let temp = lhs
//把rhs賦值到lhs
lhs = rhs
//最後把temp賦值到rhs
rhs = temp
}
//所以我們可以這樣使用就可以了:
var c = 12
var d = 13
swapValues(&c, &d)
c //13
d //12
var cString = "123"
var dString = "456"
swapValues(&cString, &dString)
cString //456
dString //123
b.泛型Function語法二:
func FName<Type>([Arguments...])->Type
代表Type的依據是透過傳出值決定
func convertTo<Type>(value: Any)->Type?{
//這裡只先做單純的轉型
//如果型別可以強轉成功,就回傳,沒有就會回傳一個Optional
return value as? Type
}
let str = "123"
let outputStr:String? = convertTo(str)
let outputInt:Int? = convertTo(str) //這裡就會回傳出nil
c.泛型Function語法三:
func FName<Type>(a: Type, [Arguments...])->Type
代表Type的依據是透過傳入值和回傳值兩個相同的Type決定
func test<Type>(a: Type)->Type?{
return a
}
let output:String? = test("124")
let output:Int? = test("123") //這一句會失敗,因為傳入的是字串,但輸出是 Int? 就不成立
Swift內建中的Function, swap就是泛型的Function,有機會可以自已查詢看看定義喔。
2. Class/Struct/Enum
Swift中,Generic也可以使用在Class/Struct/Enum,可以所定義之型態依據不同的需求產生不同的使用。常見的如Array/Dictionary就是泛型的容器用法。
語法糖衣說明:
- [Int] ==> Array
- [String:Any] ==> Dictionary
let intArray:[Int] = Array<Int>(count: 10, repeatedValue: 10)
let dict:[String:Any] = Dictionary<String, Any>(dictionaryLiteral: ("name", "grady"))
來看看泛型實際上可以幫到你什麼?
假設我們有一個Person的Struct,先不要搞得太複雜,只有
- 身份證字號
- 名字
這個例子是描寫一個可能的情境
- 身份證字號有可能是數字,也可能是字串
- 名字可以是一個單純的字串,但也可以是一個複雜的Struct/Class/Enum
Ok, 要開始囉~
struct Person<T, U> : CustomStringConvertible {
// 身份證字號,型別由外部傳入的第一個順位型別:T 決定
var identifier:T
// 姓名,型別由外部傳入的第二個順位型別:U 決定
var name:U
//實作CustomStringConvertible
var description:String{
return "\(self.identifier) : \(self.name)"
}
}
//如果身分證字號和姓名都是字串
let personByString = Person<String, String>(identifier: "124146828", name: "Grady Zhuo")
//如果身分證字號是整數和姓名是Name的struct
let personByIntName = Person<Int, Name>(identifier: 124146828, name: Name(first: "Max", last: "Chen"))
覺得寫成Person< String, Name >…很麻煩嗎?
1. 如果有建構式可以決定泛型型態,那就可以不用寫這樣的東西
let personByIntName2 = Person(identifier: 124146828, name: Name(first: "John", last: "Yeh", middle: "Boss"))
2. 另外你也可以透過 typealias 來幫你自定型別的名字
typealias StringPerson = Person<String, String>
//接下來就可以用StringPerson來建構person,而T, U就不用再進行定義囉
let person = StringPerson(identifier: "L123345678", name: "Hello world")
3. Protocol
Protocl的泛型,嚴格來說不算是標準的Generic寫法,但透過要求protocol實作者完成型別的定義,以達成模擬Generic在多型別定義的效果。
protocol Animal {
//可以透過 typealias Name 要求實作者完成Name的完整定義
typealias Type
//後續就可以使用Type去定義自已需要的參數
var type:Type { get }
}
struct Cat : Animal {
typealias Type = AnimalType
var type:Type {
var type = AnimalType()
type.科 = .貓科
return type
}
}
struct Dog : Animal {
typealias Type = String
var type:Type {
return "狗狗"
}
}
let cat = Cat()
cat.type
let dog = Dog()
dog.type
4. Extension
Extension 泛型的類型的時候,可以直接使用其泛型定義的類型
extension Person {
//傳入的name型別是U, 因為前面Person就已經宣告好了。
mutating func changeName(name: U){
self.name = name
}
}
//先產生一個grady
var grady = Person<String, String>(identifier: "123", name: "Grady")
//因為grady設定的是String,所以可以接受字串的Hello
grady.changeName("Hello")
//但下面這一句就不行了
let name = Name(first: "Hello", last: "World", middle: nil)
grady.changeName(name) //無法接受Name型別的物件