【问题标题】:Swift: Overload or rout to a generic method based on possibility of protocol conformance?Swift:基于协议一致性的可能性重载或路由到通用方法?
【发布时间】:2021-02-09 20:20:25
【问题描述】:

在通用处理链中,我有一个处理结果的方法。如果泛型 Type 恰好符合协议,我想进行不同的处理。

我想重载基于协议一致性的方法。这种切换方法可以通过编译器(如果基于协议一致性的切换没有)。

不幸的是,当从处理链中尚未为该协议键入的方法调用编译器时,编译器从不使用更具体的重载。这目前不可能吗?

这是一个例子:

protocol MyProtocol {
    var protocolString: String { get }
}

struct SpecialResult: MyProtocol {
    let protocolString: String = "special MyProtocol"
}

func preProcess<T>(result: T) {
    // Process chain for any kind of T
    print("PreProcess: T conforms to MyProtocol: \(T.self is MyProtocol.Type)") // always prints true
    process(result: result)
}

func process<T>(result: T) where T: MyProtocol { // Doesn't matter if I use process<T: MyProtocol>
    // process specifically for MyProtocol
    print("I'm processing a \(result.protocolString) result!")
}

func process<T>(result: T) {
    // process generically for T
    print("I'm processing a result.")
    print("Process: T conforms to MyProtocol: \(T.self is MyProtocol.Type)") // always prints true
}

let result = SpecialResult()
preProcess(result: result)
process(result: result)

// PreProcess: T conforms to MyProtocol: true
// I'm processing a result.
// Process: T conforms to MyProtocol: true
// I'm processing a special MyProtocol result!

我希望它会打印两次特殊的 MyProtocol 行。

对此有任何解决方案吗?即使没有方法重载,我似乎也无法让路由工作。

我可以跳过重载并使用两个不同的方法名,在 if T.self is MyProtocol.Type 后面调用每个方法名,但编译器仍然抱怨真实情况需要符合 MyProtocol,如果是真实情况,它显然必须遵守!

我可以转换result as? MyProtocol,但随后我失去了它的底层类型,这是我在整个处理链中都需要的。我真的需要一种方法来做类似result as? T: MyProtocol 之类的事情,但我似乎找不到如何...

【问题讨论】:

  • 我认为您正在想象一个不存在的层次结构或重载的使用。
  • 你是什么意思?在泛型链之外,重载路由到正确的方法。在preProcess 之外,result 显然可以是任何类型,并且可以正常工作。在preProcess 内部,result 也可以是任何类型,但它不起作用。
  • 不,它没有在通用链之外进行任何动态路由。这一切都发生在编译时。你说let result = SpecialResult() 所以编译器知道这是什么东西。因此,当您说process 时,您会得到 MyProtocol 的那个。不过,总的来说,使用where 子句进行某种重载和调度的想法是一个错误的目标。这不是 Swift 的工作方式。问题中“过载”一词的使用说明了问题。
  • 我看不出这有什么根本不同,编译器在将result 传递给process&lt;T&gt;(result: T) 时知道它的类型,但在你将它传递给preProcess&lt;T&gt;(result: T) { process&lt;T&gt;(result: T) } 时却不知道。我的意思是它适用于类型,但不是协议类型。我知道它目前没有这样做。但这似乎不是因为您陈述的原因(当不将协议与您的泛型一起使用时效果很好)。
  • @matt 至于重载是错误的方法。这是我尝试处理泛型类型 T 的第 10 种方法,如果 T: MyProtocol 稍有不同,不会将对象折叠成一个 MyProtocol,并失去其固有的 T 特性。

标签: swift generics methods overloading protocols


【解决方案1】:

这里有一些错误。最基本的是这个结构:

T.self is MyProtocol.Type

那是错误的。你的意思是:

result is MyProtocol

或者你可以使用:

T.self is MyProtocol

(但您可能应该使用result 版本)

您使用的东西大致是“T 是 MyProtocol 的类型的子类型”,这意味着“T 是一种元类型”,这真的不是你的意思(事实上,我不太确定这是否意味着任何有用)。

所以这给了你:

func preProcess<T>(result: T) {
    // Process chain for any kind of T
    print("PreProcess: T conforms to MyProtocol: \(result is MyProtocol)") // always prints true
    process(result: result)
}

由于您想要对类型进行运行时考虑,您需要使用运行时检查(即“if”语句,而不是泛型)。我不是 100% 清楚你想从中得到什么,但我希望它是这样的:

func process<T>(result: T) {
    let resultType: String
    if let myProtocol = result as? MyProtocol {
        resultType = myProtocol.protocolString
    } else {
        resultType = ""
    }

    print("I'm processing a \(resultType) result.")
    print("Process: T conforms to MyProtocol: \(result is MyProtocol)")
}

合起来就是:

PreProcess: T conforms to MyProtocol: true
I'm processing a special MyProtocol result.
Process: T conforms to MyProtocol: true
I'm processing a special MyProtocol result.
Process: T conforms to MyProtocol: true

这就是我想你的意思。当然,你可以用一个挤在一起的?? 重写大的if 语句:

let resultType = (result as? MyProtocol)?.protocolString ?? ""

或者您可以在if 中拆分您的处理,而不是使用字符串值,或其他。

你也可以这样写(可能是你的意思):

func process(result: MyProtocol) {
    print("I'm processing a \(result.protocolString) result!")
}

func process<T>(result: T) {
    // process generically for T
    if let result = result as? MyProtocol {
        process(result: result)
    } else {
        print("I'm processing a result.")
    }
}

我倾向于对使用这样的同名重载有点小心,因为它可能会模棱两可(我可能会命名第一个process(myProtocol:))。它完全有效,并且非常有用(请参阅 stdlib 中编码器容器中的各种 encode 方法)。但它也可能有点令人困惑,因此应谨慎使用。

但最重要的收获是马特的回答。泛型不是动态调度。它们是在编译时根据编译时信息 100% 确定的。通常,泛型重载只能用于提高性能(支持更好的 BidirectionalCollections 算法,而不仅仅是 Collections)。它们不应该被用来改变行为。这条路很少按照您的预期工作。

【讨论】:

  • 感谢周到的回答,点赞。不幸的是 if 条件不起作用(尽管在我的简化示例中可以)。当我执行if let result = result as? MyProtocol 时,我丢失了 actual 类型的结果,我需要在整个处理链中保留它。我真正需要的是一种投射result as? T: MyProtocol 的方法,但似乎没有办法做到这一点?还是在保留通用 T 的同时进行切换?
  • 我并不完全清楚您打算如何处理此处的“实际结果类型”,但它并没有丢失。它可以作为type(of: result) 使用。也许我不清楚您所说的“处理链”是什么意思?你能显示你想调用什么,以及你希望输出是什么吗?
  • 作为保持原始类型的例子,将process更改为print("I'm processing a \(result.protocolString) result, of type: \(type(of: result))!"),它会打印出这是一个SpecialResult,即使编译时类型是MyProtocol。但要明确:泛型不能进行动态调度。因此,如果您试图伪造自己的方式,那是行不通的。除了泛型之外,您还需要其他工具(通常是协议,但也需要 as? 转换)。
【解决方案2】:

您似乎认为这里会发生某种动态调度。它不是。这不是泛型。

泛型在编译时被解析。运行时不会基于对参数的实时检查来覆盖编译器的行为。编译器在preProcess 中所知道的只是这个东西是一个 T,它可以是任何类型。所以它选择了一般的实现。

如果想在代码中检查这是什么类型的东西,然后沮丧地告诉编译器,好吧,你可以这样做。但运行时不会为你做这些。

【讨论】:

  • 那你会怎么做路由呢?我可以跳过重载并使用两个不同的方法名,在if T.self is MyProtocol.Type 后面调用每个方法名,但编译器仍然抱怨真正的情况需要符合 MyProtocol,这显然是必须的。我可以将结果转换为?一个 MyProtocol,但后来我失去了它的基础类型......
  • 此外,当在preProcess 方法之外调用时,编译器显然会查看result 常量的类型,并正确地将其路由到正确的process 方法。所以我很困惑为什么它不能对result 做同样的观察并通过preProcess 记住它的类型。
  • @RobNapier 这会丢失通过可选链传递的基础类型。我真正需要的是将它转换为 T: MyProtocol。但我似乎找不到办法做到这一点。
  • 我正在寻找答案,但目前还不清楚您要在这里做什么。有几个明确的错误,但你的实际目标是模糊的。 (马特是 100% 正确的;我只是在编写可能是您的意思的代码)
【解决方案3】:

当你写的时候:

let result = SpecialResult()
preProcess(result: result)

编译器知道result 的类型,但在func preProcess&lt;T&gt;(result:) T 可能是任何的东西,所以它总是会调用非专业版本process&lt;T&gt;(result:)

帮助编译器保留类型信息的一种方法是添加Processor 协议:

protocol Processor {
    associatedtype T
    func description(for result: T) -> String
}

extension Processor {
    func preProcess(result: T) {
        // Process chain for any kind of T
        print("PreProcess: T conforms to MyProtocol: \(T.self is MyProtocol.Type)") // always prints true
        process(result: result)
    }

    func process(result: T) {
        // process generically for T
        print("I'm processing a \(description(for: result)).")
        print("Process: T conforms to MyProtocol: \(result is MyProtocol)") // always prints true
    }
}

这个想法是让符合类型的类型担心处理结果,这里只是描述,但实际上可以是任何东西。

现在有两种选择:

  • 所有可能结果的通用类:
struct ResultProcessor<T>: Processor {
    let result: T
    
    func description(for result: T) -> String {
        if let myProtocol = result as? MyProtocol {
            return myProtocol.protocolString
        } else {
            return "result"
        }
    }
}

用法:

let result = SpecialResult()
let processor = ResultProcessor(result: result)
processor.preProcess(result: result)
processor.process(result: result)

// PreProcess: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true

或者为每种已知类型的结果设置一个专门的类:

struct SpecialResultProcessor: Processor {
    let result: SpecialResult
    
    func description(for result: SpecialResult) -> String {
        "special \(result.protocolString) result"
    }
}

用法:

let specialResultProcessor = ResultProcessor(result: result)
specialResultProcessor.preProcess(result: result)
specialResultProcessor.process(result: result)

// PreProcess: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多