[swift 进阶]读书笔记-第九章:泛型 C9P1 重载

683 阅读3分钟

第九章:泛型 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

这一节知识点有点繁琐。 没必要一次性弄懂,多体会慢慢在实际项目中运用就可以啦~

文章源文件地址,大家如果有更好的想法和观点欢迎交流