Swift-泛型、关联类型

619 阅读4分钟

一、泛型

我们可以在函数、类、结构体、枚举中使用泛型,在名称后使用<T1, T2,...>来表示,例子如下:

// 交换两个变量的值:T1和T2是泛型
func swapValues<T1, T2>(_ a: inout T2, _ b: inout T1) {
    (a, b) = (b, a)
}

// 系统的可选类型中Wrapped是个泛型
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
}

Swift中泛型的实现原理: 如下图所示,除了参数外,还会传参数实际类型的metadata进去,metadata是参数的类型信息。 image.png

二、关联类型(Associated Type)

Swift协议中不可以用<T>来表示泛型,只能使用关联类型来做类型约束,相当于给类型一个占位名称,协议中可以有多个关联类型。

经典例子:下面是Alamofire中对于其内部方法调用.af的实现,ExtendedType是关联类型.

/// Protocol describing the `af` extension points for Alamofire extended types.
public protocol AlamofireExtended {
    /// Type being extended.
    associatedtype ExtendedType

    /// Static Alamofire extension point.
    static var af: AlamofireExtension<ExtendedType>.Type { get set }
    /// Instance Alamofire extension point.
    var af: AlamofireExtension<ExtendedType> { get set }
}

extension AlamofireExtended {
    /// Static Alamofire extension point.
    public static var af: AlamofireExtension<Self>.Type {
        get { AlamofireExtension<Self>.self }
        set {}
    }

    /// Instance Alamofire extension point.
    public var af: AlamofireExtension<Self> {
        get { AlamofireExtension(self) }
        set {}
    }
}

// 在使用时的方式:
1. 遵守协议AlamofireExtended:希望外部能通过.af来调用的类型应遵守该协议
2.外部使用时就可以用xxx.af.xxxfunc

为什么协议中要使用关联类型而不是泛型来做类型约束? 有兴趣可以查看Swift 泛型协议

三、泛型\关联类型的进一步约束

可以对泛型或者关联类型做一些遵守协议或者类型的约束,比如HandyJson的下面代码。

// 这里的T代表泛型类型需要满足遵守HandyJSON协议
public class JSONDeserializer<T: HandyJSON> {
    // ...
}

// RxSwift中的关联类型的约束
public protocol SubjectType : ObservableType {
    associatedtype Observer: ObserverType

    func asObserver() -> Observer
}

也可以使用where来添加进一步做类型约束:

// RxSwift中的Throttle类的方法使用where来进一步做类型约束
final private class Throttle<Element>: Producer<Element> {
    override func run<Observer: ObserverType>(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) 
    where Observer.Element == Element {
        let sink = ThrottleSink(parent: self, observer: observer, cancel: cancel)
        let subscription = sink.run()
        return (sink: sink, subscription: subscription)
    }
}

四、不透明类型some


protocol ZLRunable1 {
    associatedtype Speed
    var speed: Speed { get }
    // 协议的方法中有Self时,协议作为返回值也是会一样的错
//    static func ==(lhs: Self, rhs: Self) -> Bool
}

// ZLRunable1是个协议,这个方法会报错
func get(_ type: Int) -> ZLRunable1 {
    if type == 1 {
        return ZLCar()
    }
    return ZLHuman()
}

如上代码,把一个带有关联类型的协议作为一个方法的返回值时,会报错: Protocol 'ZLRunable1' can only be used as a generic constraint because it has Self or associated type requirements ,即该协议只能作为一个针对泛型来遵守的约束,因为它内部使用了Self关联类型

要想返回值是该协议的类型,且不会报错的办法有两个:

    1. 对该方法泛型 + 泛型遵守协议形式进行类型约束。
func get2<T: ZLRunable1>(_ type: Int) -> T {
    if type == 1 {
        return ZLCar() as! T
    }
    return ZLHuman() as! T
}
    1. some对返回值修饰, 然后方法内返回值只能是同一种类型, some@available(iOS 13.0.0, *)的。
@available(iOS 13.0.0, *)
func get(_ type: Int) -> some ZLRunable1 {
    return ZLHuman()
}

some这个关键字的好处是可以做到只开放协议中的内容,比如下面的r1.doSomeThing()Person类内部的,调外部用会报错;

它的弊端是只允许返回值是一个类型,如果该方法可能返回的是多个遵守该协议的类型,也是会报错Function declares an opaque return type, but the return statements in its body do not have matching underlying types

protocol ZLRunable1 {
    associatedtype Speed
    var speed: Speed { get }
    // 协议的方法中有Self时,协议作为返回值也是会一样的错
//    static func ==(lhs: Self, rhs: Self) -> Bool
}

class ZLHuman: ZLRunable1 {
    // some也可以作为一个属性的类型修饰
    @available(iOS 13.0.0, *)
    var pets: some ZLRunable1 {
        return ZLCar()
    }
    
    var speed: Int = 4
}

class ZLCar: ZLRunable1 {
    var speed: Double = 3
}

@available(iOS 13.0.0, *)
func get(_ type: Int) -> some ZLRunable1 {
    return ZLHuman()
}

func testSome() {
    if #available(iOS 13.0.0, *) {
        var r1 = get(0)
        print(r1.speed)
        
        // 不透明类型屏蔽了真实类型内部的内容,协议外的内容访问会报错
        // 这句报错:Value of type 'some ZLRunable1' has no member 'pets'
        r1.pets
    }
}
  • 3.类型擦除Type Erase

    其实就是将ZLRunable1协议改为一个具体的遵守该协议的类型作为返回值,比如结构体、类,然后将在外部通过它调用方法时,转为实际类型的调用。

/// 类型擦除
struct ZLRunable1Thunk<T>: ZLRunable1 {
    var speed: T {
        return _speed
    }
    private let _speed: T
    
    init<R: ZLRunable1>(_ runner: R) where R.Speed == T {
        _speed = runner.speed
    }
}

func get3(_ type: Int) -> ZLRunable1Thunk<Double> {

    let car = ZLCar()
    return ZLRunable1Thunk(car)
}