第九章:泛型 Generics
9.1 過載 Overloading
自由函式的過載 Overload Resolution for Free Functions
這一小段講的主要是泛型的過載
相關知識點
/// 泛型列印view的相關資訊
///
/// - Parameter view: view
func log<View: UIView>(_ view: View) {
print("It's a \(type(of: view)), frame: \(view.frame)")
}
///對泛型的重寫
func log(_ view: UILabel) {
let text = view.text ?? "(empty)"
print("It's a label, text: \(text)")
}
let label = UILabel(frame: .zero)
label.text = "liaoworking is handsome~~"
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 101))
log(label) //It's a label, text: liaoworking is handsome~~
log(button) //It's a UIButton, frame: (0.0, 0.0, 100.0, 101.0)
複製程式碼
上面這個demo沒毛病對吧,只要我們過載了一個泛型方法, 在列印對於的型別時就呼叫這個方法。
那我們看看上面方法的下面的使用場景
let views = [label, button] // Type of views is [UIView] for view in views {
for view in views {
log(view)
}
/*
It's a UILabel, frame: (20.0, 20.0, 200.0, 32.0)
It's a UIButton, frame: (0.0, 0.0, 100.0, 50.0)
*/
複製程式碼
咦~為嘛在for迴圈中就無法去區分方法了
??? 手動黑人問號臉。
原因:泛型的過載在編譯時期
就確定的。並不是在runtime時候動態確定。 就會有上面的差異。
運算子的過載 Overload Resolution for Operators
場景:我們想要自己封裝一個冪運算
的方法
precedencegroup tt{//優先順序定義
associativity: left //結合方向
higherThan: MultiplicationPrecedence //高於乘法型別
}
infix operator **: tt
func **(lhs: Double, rhs: Double) -> Double {
return pow(lhs, rhs)
}
func **(lhs: Float, rhs: Float) -> Float {
return powf(lhs, rhs)
}
2*2.0**3.0
複製程式碼
先寫一個冪運算的泛型demo
func **<i: SignedInteger>(lhs: i, rhs: i) -> i {
let result = Double(lhs.max) ** Double(rhs.max)
return numericCast(IntMax(result))
}
2**3 ///Error: Ambiguous use of operator
複製程式碼
可在實際編譯過程中卻編譯不過
原因:
對於過載運算子,編譯器會使用非泛型
版本,不考慮泛型版本
解決:
至少將一個引數顯示的宣告
為Int型別,或者將返回值宣告
為Int型別即可
let intResult: Int = 2 ** 3 //8
複製程式碼
這種編譯器的行為只是對運算子
生效,swift團隊對於效能上的考量
選擇了這種可以降低型別檢查器的複雜度的方案。
使用泛型約束進行過載
書中舉的例子是檢查一個集合是不是另外一個集合的子集
swift標準庫中有一個方法isSubSet
提供了這功能,但只是滿足於Set這種滿足了SetAlgebra
協議的型別。
我們自己去實現這個功能時注意函式的複雜度,for迴圈遍歷會使複雜度變成O(m * n)
.
如果序列元素滿足Hashable我們可以通過Set(array)
陣列轉化為Set,再去用isSubSet去獲得結果。
書中給出的最優解決方案是如下
///不需要是同一種型別 只需要other遵守Sequence的任意型別就可以了,其複雜度也是成線性增長。
extension Sequence where Iterator.Element: Hashable {//泛型版本
func isSubset<S: Sequence>(of other: S) -> Bool
where S.Iterator.Element == Iterator.Element {
let otherSet = Set(other)
for element in self {
guard otherSet.contains(element) else {
return false
}
}
return true
}
複製程式碼
這樣我們寫的函式就有更多的可能性
,可以傳入一個數字的countableRange來進行檢查
[5,4,3].isSubSet(of:1...10)//true
複製程式碼
使用閉包對行為進行引數化---這小段的知識點對於函式的封裝
有很大的啟發。
針對於上面的demo我們二次引申
:
讓isSubset針對於不是Equatable
的型別也適用,
我們可以傳一個返回值是bool的函式來表明元素是否相等。
swift標準庫中的contains
方法就是這麼做的
其具體使用如下:
let isEven = {$0 % 2 == 0}
(0..<5).contains(where:isEven) //true
[1,3,5,7,9].contains(where:isEven) == false
複製程式碼
我們可以類似於contain的使用去寫一個更靈活的isSubset
extension Sequence {
func isSubset<S: Sequence>(of other: S,
by areEquivalent: (Iterator.Element, S.Iterator.Element) -> Bool)
-> Bool {
for element in self {
guard other.contains(where: {areEquivalent(element, $0)}) else{return false}
}
return true
}
}
複製程式碼
只要你提供閉包能夠處理的比較操作
,兩個序列的元素就算不是同種型別
的也可以進行對比。
let ints = [1,2]
let strings = ["1","2","3"]
ints.isSubset(of:Strings) { String($0) == $1 } //true
複製程式碼
這一節知識點有點繁瑣。 沒必要一次性弄懂,多體會慢慢在實際專案中運用就可以啦~