【问题标题】:Swift Result<> cannot convert to downcast resultSwift Result<> 无法转换为向下转换的结果
【发布时间】:2020-11-10 10:39:21
【问题描述】:

我在函数完成块中使用了 swift Result&lt;&gt; 泛型,但将其传递给具有协议类型的另一个完成块无法按预期工作。

public protocol Journey { }
class CommsJourney: Journey { }

typealias Completion = (Result<[Journey]?, Error>) -> Void
typealias CommsCompletion = (Result<[CommsJourney]?, Error>) -> Void

class Comms {
    func refreshTimes(completion: @escaping CommsCompletion) {
        
        let array = [CommsJourney()]
        completion(.success(array))
    }
}

/**
 Completion block returns `Journey` protocol if successful
 */
func refreshTimes(completion: @escaping Completion) {
    
    // Passing the previous completion block fails to compile as signatures are different
//    Comms().refreshTimes(completion: completion)
    
    Comms().refreshTimes { (result) in
        
        switch result {
        case .success(let results):
            // result is `CommsCompletion` yet returns perfectly fine
            completion(.success(results))
        case .failure(let error):
            completion(.failure(error))
        }
    }
}

无法转换“完成”类型的值(又名 '(Result, Error>) -> ()') 达到预期 参数类型“CommsCompletion”(又名 '(Result, Error>) -> ()')

我想了解为什么传递完成不起作用,但发送结果却可以。还有什么是纠正这个问题的最佳方法?我的switch 示例是最简单/最好的解决方案吗?

编辑----

有人向我指出这个 q/a Swift generic coercion misunderstanding 要么完全超出我的想象,要么不是针对我的情况。因为我可以通过简单地提取result 然后转发它来解决这个问题,创建一个中间Any... 对象似乎有点过分。

编辑 ----- 游乐场要点在这里https://gist.github.com/ddaddy/d58b648dbe82a1c63fe23541cc1aad40

【问题讨论】:

  • 我不明白你怎么会感到困惑。 :) 完成不是 CommsCompletion。因此,当它需要 CommsCompletion 时,您不能使用 Completion 调用 refreshTimes。困难的部分是什么?我要香蕉,你给我大象;那不编译。完毕。 (在起作用的那个中,它与开关无关。您可以删除整个主体,它仍然可以编译。只是您更改了签名,以便refreshTimes 期望完成。我要一头大象,而您给我一头大象。)
  • 好的,我明白了,虽然它们是同一个协议,但它们是不同的完成类型签名。我没有清楚地写我的(有效的),所以我编辑了它。我可以成功地将完成结果从一种完成块类型传递给另一种。
  • 所以 Journey 是一个协议? — 您能否使用实际代码进行编辑,而不仅仅是使用我们必须自己猜测和填写的名称的摘录?如果您需要实际帮助,请提供minimal reproducible example
  • 是的。我很抱歉我错过了。实际的代码是一个非常大的代码库的一部分,所以我不得不把它去掉。我通常很擅长我的详细信息,但肯定是度过了一个愉快的早晨。
  • 好吧,我不会改变我所说的任何内容。您是说,在class Comms 中,您声明了func refreshTimes(completion: @escaping CommsCompletion) { },但它无法编译。如果您将 CommsCompletion 更改为 Completion,则可以。函数调用中的类型和该函数声明中的类型必须匹配。函数的内容与故事无关。

标签: swift generics swift-protocols


【解决方案1】:

好的,所以这不会编译:

public protocol Journey { }
class CommsJourney: Journey { }

typealias CommsCompletion = (Result<[CommsJourney]?, Error>) -> Void

class Comms {
    func refreshTimes(completion: @escaping CommsCompletion) { }
}

typealias Completion = (Result<[Journey]?, Error>) -> Void

func callerOfRefreshTimes(completion: @escaping Completion) {
    Comms().refreshTimes(completion: completion) // nope
}

那是因为您试图在需要 CommCompletion 函数的地方传递 Completion 函数,并且它们是完全不同的函数类型,因此存在类型不匹配并且编译器会报错。

要了解为什么您的第二个示例确实有效,让我们对其进行简化。所以,这编译得很好:

public protocol Journey { }
class CommsJourney: Journey { }

typealias CommsCompletion = (Result<[CommsJourney]?, Error>) -> Void

class Comms {
    func refreshTimes(completion: @escaping CommsCompletion) { }
}

typealias Completion = (Result<[Journey]?, Error>) -> Void

func callerOfRefreshTimes(completion: @escaping Completion) {        
    Comms().refreshTimes { _ in
        let r : Result<[Journey]?, Error> = .success([CommsJourney]()) // <--
        completion(r)
    }
}

我们所做的只是创建一个正确类型的结果,并将其传递给completion 函数调用。确实,我们使用 CommsJourney 数组形成 Result,而不是 Result 类型所要求的 Journey 数组,但这是实际的多态性:您可以提供需要超类型数组的子类型。

所以在第二个代码中,我们确实只对数组使用了多态替换。但我们确实在别处使用任何多态替换。特别是,我们正在创建的 Result 的类型 恰好completion 函数期望作为参数的类型。你在你的代码中做同样的事情,但是你是通过隐式类型来做的,所以它不是那么明显。

当然,在您的代码中,值result 作为周围闭包的参数到达只是一个红鲱鱼!您在代码中做的第一件事就是将 Result 拉开并提取其中的数组。然后你把原来的 Result 扔掉!

因此,您不要尝试传递错误的 Result 类型,也不要尝试强制 Result;相反,您只需扔掉第一个结果并制作一个全新的结果正确的类型)并传递它(这就是为什么我写了我的代码来演示这样做)。

【讨论】:

  • 明白。感谢您的简化回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多