从实例调用方法时,方法上的Swift泛型约束的行为不符合预期



以下是Swift代码:

class HTTP {
func run<T: Decodable>(handler: (Result<T, Error>) -> Void) {
HTTP.handle(handler: handler)
}
}
extension HTTP {
static func handle<T: Decodable>(handler: (Result<T, Error>) -> Void) {
Swift.print("Base")
}
}
extension HTTP {
static func handle<T: Decodable & Offline>(handler: (Result<T, Error>) -> Void) {
Swift.print("Offline")
}
}
protocol Offline {
associatedtype Data
var data: Data { get set }
}
struct Model: Decodable, Offline {
var data: String = "abc..."
}
let h1 = HTTP()
h1.run { (r: Result<[String], Error>) in }        // 1 - Print "Base" => OK
let h2 = HTTP()
h2.run { (r: Result<Model, Error>) in }           // 2 - Print "Base" => ???
HTTP.handle { (r: Result<[String], Error>) in }   // 3 - Print "Base" => OK
HTTP.handle { (r: Result<Model, Error>) in }      // 4 - Print "Offline" => OK

我试图弄清楚为什么在情况2中;基本;而不是";脱机";。如果有人有建议,则根据给定的类型调用正确的handle方法T

为了演示/运行,我将handle方法设置为静态方法,以表明它在静态上下文中工作(情况3和4(。正如您所看到的,从HTTP实例调用的上下文行为是不同的(情况2(。

知道吗?

它看起来像实例函数run(handler:),因为它将T设置为仅为Decodable的约束,并以某种方式过滤Offline约束。因此,将一个仅为Decodable的类型传递给静态函数handle(handler:),编译器假设可调用函数是仅以Decodable为约束的函数。我不知道这是否可以理解。

您可以通过在HTTP类中为实例函数添加重载来获得预期的行为:

class HTTP {
func run<T: Decodable>(handler: (Result<T, Error>) -> Void) {
HTTP.handle(handler: handler)
}

func run<T: Decodable & Offline>(handler: (Result<T, Error>) -> Void) {
HTTP.handle(handler: handler)
}
}

要调用的正确HTTP.handle在编译时决定,并烘焙到二进制文件中。CCD_ 12仅被承诺为CCD_ 14中的CCD_。它可能符合其他协议,但这就是承诺的全部。因此,它将其编译为对Decodable版本的调用,即使您使用可能是Offline的东西来调用它。

这就到了通用专业化的地步。这并不是为了改变行为。它通常旨在提高性能。例如,考虑以下内容:

func f<Seq: Sequence>(_ seq: Seq) -> Bool { ... }
func f<Seq: RandomAccessCollection>(_ seq: Seq) -> Bool { ... }

每个RandomAccessCollection都是一个序列,因此可能会为数组调用其中一个。两者都应始终返回相同的结果。但在系统可以证明Seq是Array的情况下,第二种方法可能更有效。如果他们返回了不同的结果,就不会正常工作。

泛型不是重塑类继承的方法。如果您确实需要类继承,那么就使用类和继承。但一般来说,你应该重新设计你的系统来避免这种情况。如何做到这一点取决于这里的真正目标。

最新更新