为什么警卫打破类型推断?

时间:2018-05-31 13:02:33

标签: ios swift type-inference swift-playground

我遇到了类似标题所描述的问题:由于某种原因,guard语句打破了类型推断。我创建了一个Playground项目来玩。

以下是一些用于设置的样板代码:

import Foundation

struct Model: Decodable {
    var i: String
}

let jsonString = """
{
    "i": "yes"
}
"""

let jsonData = jsonString.data(using: .utf8)
let theModel = try JSONDecoder().decode(Model.self, from: jsonData!)

struct Response<T> {
    var decodedData: T?
}

enum Result<Value> {
    case success(Value)
    case failure
}

struct Client {
    static let shared = Client()
    private init() {}

    func execute<T: Decodable>(completion: (Response<T>) -> ()) {
        let decodedData = try! JSONDecoder().decode(T.self, from: jsonData!)
        completion(Response(decodedData: decodedData))
    }
}

问题在于:

struct ServicesA {
    func loadSomething(completion: (Result<Model>) -> ()) {
        Client.shared.execute { result in      // error: generic parameter 'T' could not be inferred
            guard let decodedData = result.decodedData else { return }
            completion(Result.success(decodedData))
        }
    }
}

struct ServicesB {
    func loadSomething(completion: (Result<Model>) -> ()) {
        Client.shared.execute { result in
            completion(Result.success(result.decodedData!))
        }
    }
}

ServicesA休息而ServicesB编译。

正如您所看到的,唯一的区别是guard let decodedData = result.decodedData else { return }。它打破了类型推断,因此Client.shared.execute函数会抱怨无法推断出T

我想知道为什么会发生这种情况,以及解决这个问题最合适的方式。

4 个答案:

答案 0 :(得分:4)

TLDR;单行闭包编译与多行

不同

答案很长:让我们暂时忘记单行关闭。当我们编写接受泛型作为参数类型然后调用此函数的泛型函数时,编译器需要在调用时知道所需函数的类型,即其参数的类型。现在考虑这个论点是一个闭包。编译器需要再次知道调用时闭包的类型(函数签名)。这个信息应该在闭包的签名中可用(即参数和返回类型),编译器不担心(并且正确地)关于闭包的主体。因此,服务A的表现完全符合编译器的预期,即闭包签名不会给出任何类型的线索。

现在有单线关闭。 Swift编译器仅针对单行闭包具有内置类型推理优化。当你编写单行闭包时,编译器首先启动它的类型推断并尝试从其唯一的单体系中找出闭包,包括它的返回类型等。这种类型的推理可以用于单线闭包(有或没有泛型的上下文)。 此类型推断是您的服务B工作的原因

所以我将这个问题改为“为什么类型推断适用于单行闭包?”因为这是Swift为单行闭包提供的额外功能。一旦你转向多行闭包(它不是特定的Guard语句,如果你也放置print(“hello world”)也会发生同样的事情),这种类型推断不再可用。虽然可能有其他类型的推理检查可用于多行闭包。

您可以做的只是在闭包签名中提供类型信息 即(结果:响应&lt; Model&gt;)

答案 1 :(得分:3)

当闭包中只有一个语句时,编译器只会推断闭包返回类型。

let x = {
    return 1
}()

                // x == Int(1)

let y = {       // error: unable to infer complex closure return type; add explicit type to disambiguate
    print("hi")
    return 2
}()

https://forums.swift.org/t/problems-with-closure-type-inference/11859/2

答案 2 :(得分:0)

问题是你引用data变量,但编译器不知道这是什么类型(因为它是Generic)。试试这个:

struct ServicesA {
    func loadSomething<T:Model>(completion:(Result<T>) -> ()) {
        Client.shared.execute { result:Response<T> in
            guard let decodedData = result.decodedData else { return }
            completion(Result.success(decodedData))
        }
    }
}

答案 3 :(得分:0)

您需要使用结果&lt;来强制转换它result模型&gt; 因此编译器可以了解T

struct ServicesA {
    func loadSomething(completion:(Result<Model>) -> ()) {
        Client.shared.execute { (result:Response<Model>) in
            guard let data = result.decodedData else {return}
            completion(Result.success(data))
        }
    }
}