在 Swift 泛型中使用正确的类型约束变体

问题描述

给定一个具有两个泛型和多个类型约束初始化器的类型:

struct ComposableRequest <T,U> {

    init() {
        self.t = defaultT()
        self.u = defaultU()
    }
    
    var t: T?
    var u: U?

    private func defaultT() -> T? {
        nil
    }
    
    private func defaultT() -> T? where T == String {
        "Test1"
    }
    
    private func defaultT() -> T? where T == Int {
        1
    }
    
    private func defaultU() -> U? {
        nil
    }
    
    private func defaultU() -> U? where U == String {
        "Test2"
    }
    
    private func defaultU() -> U? where U == Int {
        2
    }
}

我似乎永远无法获得认电镀方法的类型约束版本:

let first = ComposableRequest<String,Int>()
print(first.t) // I'd expect this to be `"Test1"`,but its nil!
print(first.u) // I'd expect this to be `2`,but its nil!

当多个类型约束选项都满足时,Swift 如何决定使用哪种类型约束方法?如果所选方法不明确,此代码是否应该编译?更专业的版本如何调用

解决方法

此时的问题是,您正在做的事情几乎没有什么通用的东西了。正是出于这个原因,您可以轻松地获得之后的结果,而无需尝试使用 where 子句作为一种假重载:

struct MyGeneric<T,U> {

    init() {
        self.t = self.defaultT()
        self.u = self.defaultU()
    }
    
    var t: T?
    var u: U?

    private func defaultT() -> T? {
        if T.self is String.Type {
            return Optional("Test" as! T)
        }
        if T.self is Int.Type {
            return Optional(1 as! T)
        }
        return nil
    }
    
    private func defaultU() -> U? {
        if U.self is String.Type {
            return Optional("Test2" as! U)
        }
        if U.self is Int.Type {
            return Optional(2 as! U)
        }
        return nil
    }
}
,

在编译时静态选择重载。再加上结构体方法是静态分派的事实,意味着当编译器看到时,已经决定调用哪个defaultT/defaultU

self.t = defaultT()
self.u = defaultU()

当这些行运行时不会。从编译器的角度来看,TU 可以是任何类型,因此此处选择的最佳方法是返回 nil 的方法。

调用其他方法的一种方法是也约束初始化器:

init() where T == String,U == Int {
    self.t = defaultT()
    self.u = defaultU()
}

init() where T == Int,U == String {
    self.t = defaultT()
    self.u = defaultU()
}

init() where U == String {
    self.t = defaultT()
    self.u = defaultU()
}

init() where U == Int {
    self.t = defaultT()
    self.u = defaultU()
}

init() where T == String {
    self.t = defaultT()
    self.u = defaultU()
}

init() where T == Int {
    self.t = defaultT()
    self.u = defaultU()
}

init() {
    self.t = defaultT()
    self.u = defaultU()
}

但那是很多重复...每次您想添加对 StringInt 之外的新类型的支持时,您都需要添加更多的初始化程序...>

一种更好的方法是将 defaultX 方法移动到名为 HasDefault 的协议中:

protocol HasDefaults {
    static func defaults() -> Self
}
extension String: HasDefaults {
    static func defaults() -> [String] {
        ["Test1","Test2"]
    }
    
}

extension Int: HasDefaults {
    static func defaults() -> [Int] {
        [1,2]
    }
}

struct MyGeneric<T,U> {
    init() {
        t = nil
        u = nil
    }
    
    init() where T: HasDefaults {
        t = T.defaults()[0]
        u = nil
    }
    
    init() where U: HasDefaults {
        t = nil
        u = U.defaults()[1]
    }
    
    init() where T: HasDefaults,U: HasDefaults {
        t = T.defaults()[0]
        u = U.defaults()[1]
    }
    
    var t: T?
    var u: U?
}

您甚至可以选择不允许将任何非HasDefaults 类型用作 TU。这样你只需要一个初始化器。