美文网首页
Swift5.1学习随笔之泛型Generics

Swift5.1学习随笔之泛型Generics

作者: SAW_ | 来源:发表于2020-05-07 11:59 被阅读0次
    泛型函数
    • 泛型可以将类型参数化,提高代码复用率,减少代码量

    看个例子🌰,交换两个变量的值:定义一个函数,参数为inout参数,内部元组实现交换两个外部传入的变量

    func swapValue(_ a: inout Int, _ b: inout Int) {
        (a, b) = (b, a)
    }
    var n1 = 10
    var n2 = 20
    swapValue(&n1, &n2)
    print(n1, n2) // 20 10
    

    上面的swapValue函数的参数只支持Int类型,用局限性,如果要支持其他类型的参数,就必须写多个函数,很麻烦,严重增加代码量,这时候就需要参数支持泛型,能传入任意类型的参数。

    这里的T代表一种不确定的类型:(这里的T随便自定义,通常用T,代表type)

    func swapValue<T>(_ a: inout T, _ b: inout T) {
        (a, b) = (b, a)
    }
    
    var n1 = "abc"
    var n2 = "123"
    swapValue(&n1, &n2)
    print(n1, n2) //123 abc
    
    var n1 = true
    var n2 = false
    swapValue(&n1, &n2)
    print(n1, n2) //false true
    
    var n1 = 10.1
    var n2 = 90.23
    swapValue(&n1, &n2)
    print(n1, n2) //90.23 10.1
    

    其实swift内部就已经提供了一个swap交换的方法,我们点进去看下就可以知道我们自定义的swapValue跟它的定义方法是一样的:

    /// Exchanges the values of the two arguments.
    ///
    /// The two arguments must not alias each other. To swap two elements of a
    /// mutable collection, use the `swapAt(_:_:)` method of that collection
    /// instead of this function.
    ///
    /// - Parameters:
    ///   - a: The first value to swap.
    ///   - b: The second value to swap.
    @inlinable public func swap<T>(_ a: inout T, _ b: inout T)
    
    • 泛型函数赋值给变量
    var n1 = 10
    var n2 = 20
    var fn: (inout Int, inout Int) -> () = swapValue
    fn(&n1, &n2)
    
    func test<T1, T2>(_ t1: T1, _ t2: T2) { }
    var fn: (Int, Double) -> () = test
    

    泛型类型

    结构体、类、枚举也可以增加泛型定义

    举个例子,利用数组实现一个栈,类Stack后面加个<E>,代表支持任意类型的元素操作

    class Stack<E> {
        var elements = [E]()
        func push(_ element: E) { elements.append(element) }
        func pop() -> E { elements.removeLast() }
        func top() -> E { elements.last! }
        func size() -> Int { elements.count }
    }
    
    var intStack = Stack<Int>() //支持Int的栈
    var stringStack = Stack<String>() //支持String的栈
    var anyStack = Stack<Any>() //支持Any的栈
    

    存在继承的写法:

    class SubStack<E>: Stack<E> {
        
    }
    

    struct中自定义需要加上mutating

    struct Stack<E> {
        var elements = [E]()
        mutating func push(_ element: E) { elements.append(element) }
        mutating func pop() -> E { elements.removeLast() }
        func top() -> E { elements.last! }
        func size() -> Int { elements.count }
    }
    

    枚举中定义泛型

    enum Score<T> {
        case point(T)
        case grade(String)
    }
    let score0 = Score.point(10)
    let score1 = Score<Int>.point(10)
    let score2 = Score<Double>.point(10.1)
    
    let score3 = Score<Int>.grade("C")
    

    关联类型 Associated Type
    • 给协议中用到的类型定义一个占位名称

    定义一个协议Stackable,声明几个栈的常规操作,如果要实现一个栈,遵循Stackable协议,内部自己去实现

    protocol Stackable {
        associatedtype Element //关联类型(可以理解泛型)
        mutating func push(_ element: Element)
        mutating func pop() -> Element
        func top() -> Element
        func size() -> Int
    }
    

    协议中实现泛型无法像类、结构体、枚举那样,只能用associatedtype

    protocol Stackable<Element> { }
    //报错:Protocols do not allow generic parameters; use associated types instead
    
    • 协议中可以拥有多个关联类型
    protocol Stackable {
        associatedtype Element //关联类型
        associatedtype Element2 //关联类型
        associatedtype Element3 //关联类型
    }
    

    具体应用中的实现:
    声明一个类StringStack,遵循Stackable协议,设置真实关联类型为String类型。

    class StringStack: Stackable {
        //给关联类型设置真实类型
        typealias Element = String
        //...
    }
    

    因为编译器会自动处理,其实可以不用写明typealias Element = String,只要实现协议方法的时候写明是String就行,编译器会自动关联到需要的类型

    class StringStack: Stackable {
        //给关联类型设置真实类型
    //    typealias Element = String
        var elements = [String]()
        func push(_ element: String) { elements.append(element) }
        func pop() -> String { elements.removeLast() }
        func top() -> String { elements.last! }
        func size() -> Int { elements.count }
    }
    

    想要更灵活的实现一个泛型的类,可以在定义class的时候设置泛型:

    class Stack<E>: Stackable {
    //    typealias Element = E
        var elements = [E]()
        func push(_ element: E) { elements.append(element) }
        func pop() -> E { elements.removeLast() }
        func top() -> E { elements.last! }
        func size() -> Int { elements.count }
    }
    

    类型约束

    下面的代码中,对于泛型T设置了一些约束,参数泛型T必须是Person类或者子类且遵循了Runnable协议的。

    protocol Runnable { }
    class Person { }
    func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
        (a, b) = (b, a)
    }
    

    下面的代码中,协议里面的关联类型遵守Equatable协议,那么遵守Stackable协议的类的泛型也必须遵守Equatable协议。

    protocol Stackable {
        associatedtype Element: Equatable
    }
    class Stack<E: Equatable>: Stackable {
        typealias Element = E
    }
    

    下面的代码中,S1S2必须遵守Stackable协议,且S1.Element == S2.Element,同时S1.Element要遵守Hashable协议。

    func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool
        where S1.Element == S2.Element, S1.Element: Hashable {
        return false
    }
    
    var s1 = Stack<Int>()
    var s2 = Stack<Int>()
    var s3 = Stack<String>()
    equal(s1, s2) //编译正确
    equal(s1, s3) //编译错误:Global function 'equal' requires the types 'Stack<Int>.Element' (aka 'Int') and 'Stack<String>.Element' (aka 'String') be equivalent
    

    协议类型的注意点

    定义Runnable协议,定义类PersonCar遵守Runnable协议。
    可以看到变量r1Person对象,r2Car对象。
    但是编译器并不知道r1r2具体哪个是Person,哪个是Car,因为get返回值统一是Runnable类型

    protocol Runnable { }
    class Person: Runnable { }
    class Car: Runnable { }
    
    func get(_ type: Int) -> Runnable {
        if type == 0 {
            return Person()
        }
        return Car()
    }
    
    var r1 = get(0)
    var r2 = get(1)
    

    接下来给协议Runnable引入关联类型associatedtype:声明关联类型Speed,有一个speed只读的计算属性

    protocol Runnable {
        associatedtype Speed
        var speed: Speed { get }
    }
    class Person: Runnable {
        var speed: Double { 0.0 }
    }
    class Car: Runnable {
        var speed: Int { 0 }
    }
    
    • 用泛型解决问题
    func get<T: Runnable>(_ type: Int) -> T {
        if type == 0 {
            return Person() as! T
        }
        return Car() as! T
    }
    var r1: Person = get(0)
    var r2: Car = get(1)
    
    • 用不透明类型some解决问题
    func get(_ type: Int) -> some Runnable {
        return Car()
    }
    

    some限制只能返回一种类型,因此编译器能确定最后返回的是什么类型,编译就能通过。

    相关文章

      网友评论

          本文标题:Swift5.1学习随笔之泛型Generics

          本文链接:https://www.haomeiwen.com/subject/gemoghtx.html