本篇將詳細總結介紹Swift泛型的用法;
Swift泛型代碼讓你能夠根據自定義的需求,編寫出適用于任意類型、靈活可重用的函數及類型。它能讓你避免代碼的重復,用一種清晰和抽象的方式來表達代碼的意圖。
主要內容:
1.泛型解決的問題
2.泛型函數
3.泛型類型
4.擴展一個泛型類型
5.泛型的類型約束
6.關聯類型
一、泛型解決的問題
Swift泛型代碼讓你能夠根據自定義的需求,編寫出適用于任意類型、靈活可重用的函數及類型。它能讓你避免代碼的重復,用一種清晰和抽象的方式來表達代碼的意圖。這種說法很模糊,下面我們結合一個示例來說明泛型的作用。
需求描述:使用函數來交換兩個變量的值
//互換兩個整型
func swapTwoInt(a:inout Int , b:inout Int){
(a, b) = (b, a)
}
//互換兩個Double
func swapTwoDouble(a:inout Double, b:inout Double){
(a,b) = (b,a)
}
代碼分析:
swapTwoInt與swapTwoDouble兩個函數功能相同,唯一的區別就是傳入的變量類型不同。這樣的代碼看起來重復又累贅。在實際應用中,通常需要一個更實用更靈活的函數來交換兩個任意類型的值,幸運的是,泛型代碼幫你解決了這種問題。
二、泛型函數
泛型函數可以適用于任何類型,下面的swapTwoValues(::)函數是上面兩個函數的泛型版本,可以交換任意類型的兩個變量。
尖括號里聲明一種通用類型T,參數列表里可以使用這種類型名表示通用類型
func SwapTwoThing<T>(a:inout T, b:inout T){
(a, b) = (b, a)
}
var a = 100
var b = 200
swapTwoInt(a: &a , b: &b)
a //200
b //100
var string1 = "hello"
var string2 = "world"
SwapTwoThing(a: &string1, b: &string2)
string1 //world
string2 //hello
總結泛型函數的使用:
1.使用了占位類型名(T),來替換實際類型名(Int,Double);
2.占位類型符并不指定T必須是什么類型,但是卻限制了參數a和b必須是同一種類型T;
3.只有SwapTwoValues<T>(:)函數在調用時,才能根據所傳入的實際類型決定T所代表的類型;
4.T只是一個符號,可以使用大寫字母開頭的駝峰命名法(例如T和MyTypeParameter)來為類型參數命名,以表明它們是占位類型,而不是一個值。
三、泛型類型
3.1.系統類型使用到的泛型
事實上,泛型類型的使用貫穿了Swift語言。例如,Swift的Array和Dictionary都是泛型集合。你可以創建一個Int數組,也可創建一個String數組。
let arr = Array<Int>()
let dict = Dictionary<String,Int>()
let set = Set<Float>()
3.2.自定義泛型類型:實現一個棧結構體
除了泛型函數,Swift還允許你定義泛型類型;這些自定義類、結構體和枚舉可以適用于任何類型,類似于Array和 Dictionary。下面的示例就是創建一個具有棧功能的結構體,適用于各種類型。
struct Stack<Element>{
//存放棧中變量的數組
var items = Array<Element>()
//入棧:向棧中添加一個新元素
mutating func push(item:Element){
items.append(item)
}
//出棧:刪除棧頂元素,并返回此元素
mutating func pop() ->Element?{
return items.removeLast()
}
}
var stack = Stack<Int>()
stack.push(item: 11)
stack.push(item: 22)
stack.pop() //22
var stack1 = Stack<String>()
stack1.push(item:"aaa")
stack1.push(item:"bbb")
stack1.pop() //“bbb"
3.3.自定義泛型類型:多個占位符
自定義泛型類型可以設置多個類型占位符,下面就是自定義了一個泛型類型Pair,它具有兩個占位類型符。
struct Pair<T1, T2>{
var t1:T1
var t2:T2
}
var pair1 = Pair(t1: "hello", t2: "hi")
print(pair1) //Pair<String, String>(t1: "hello", t2: "hi")
var pair2:Pair<String, Int> = Pair(t1:"hello",t2: 123)
print(pair2) //Pair<String, Int>(t1: "hello", t2: 123)
四、擴展一個泛型類型
擴展一個泛型類型,可以直接使用原始類型定義中聲明的類型參數列表,并且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用。
比如,我們現在擴展泛型類型Stack,為其添加計算型屬性topItem,用于獲取棧頂元素,代碼示例如下:
extension Stack {
//返回當前棧頂元素而不會將其從棧中移除
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
var stack3 = Stack<Int>()
stack3.push(item:1)
stack3.push(item:2)
stack3.push(item: 3)
if let topItem = stack3.topItem{
print("棧頂元素:\(topItem)") //棧頂元素:3
}
注意:擴展中的占位類型符需要與原始類保持一致,所以這里用的還是Element。
五、泛型的類型約束
swapTwoValues(::)函數和Stack類型可以作用于任何類型。但如果可以為泛型函數和泛型類型的類型添加一個特定的類型約束,將會是非常有用的。
通常情況下,我們設置泛型類型約束的時候,會指定一個類型參數必須繼承自指定類,或者符合一個特定的協議或協議組合。
5.1.類型約束語法
對泛型函數添加類型約束的基本語法如下所示(作用于泛型類型時的語法與之相同)。
//在一個類型參數名后面放置一個類名或者協議名,并用冒號進行分隔,來定義類型約束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這里是泛型函數的函數體部分
}
5.2.泛型類型約束實踐
下面的泛型函數用于查找數組中某個元素的索引位置;但由于for循環里用到了對象比較"==",要確保所有的類型都適用,所以在泛型函數的中添加了類型約束,使用此泛型函數的參數必須遵循Equatable協議。
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25]) //nil
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"]) //2
注意:Swift標準庫定義了Equatable協議,該協議要求任何遵循該協議的類型必須實現等式符(==)及不等符(!=)。從而能對該類型的任意兩個值進行比較。所有的Swift標準類型自動支持 Equatable 協議
六、關聯類型
關聯類型是在為協議中的某個類型提供一個占位名,其所代表的實際類型會在協議被采納時才會被指定。這里涉及到兩個關鍵字,其作用就是給一個類型起一個別名,首先來說明一下:
associatedtype(協議聲明中使用)
typealias (協議實現中使用)
下面通過一個示例來理解關聯類型的作用:定義一個可稱重的協議,其中使用了泛型關聯類型。這種方式可以更大程度的使用協議,具體實現協議的時候再決定類型。
protocol WeightCaclulable{
//associatedtype設置別名,即關聯類型
associatedtype WeightType
var weight:WeightType{get} //返回重量屬性,其類型是WeightType
}
//iphone7:手機較輕,表示重量時會有小數點,所以使用Double描述
class Iphone7:WeightCaclulable{
//實現的時候用的是typealias
typealias WeightType = Double
var weight: Double {
return 0.114
}
}
//Ship:輪船較重,表示重量可以忽略小數,所以使用Int描述
class Ship:WeightCaclulable{
typealias WeightType = Int
var weight: WeightType
init(weight:WeightType) {
self.weight = weight
}
}
let iphone7 = Iphone7()
print(iphone7.weight) //0.114
let ship = Ship(weight: 100000)
print(ship.weight) //100000
6.1.關聯類型添加約束
協議中存在關聯類型,我們也可以為其添加約束,下面是一個Container協議,我們設置其關聯類型Item遵循了協議Equatable,具體代碼如下:
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}