无法使用匹配类型转换类型的值 - 我是否遇到了编译器限制?

问题描述 投票:3回答:1

背景:

我正在构建一个带有服务层的应用程序,该服务层应返回相似的模型(例如,所有具有text: String属性,在协议TextModel中定义)。该服务拥有一个存储库,用于查找和返回符合TextModel的具体类型的模型。存储库需要保留内部工作的具体类型信息。我希望保持服务免受模型的具体类型的影响,因此我不必为每种模型类型重复它。编译器不允许我这样做......

问题:

我在Playground中简化的以下代码将无法编译:

enum Result<T> {

    case success(T)
    case error
}

// Model Layer

protocol TextModel {

    var text: String { get }
}

struct Person: TextModel {

    let text: String
}

// Service Layer

class TextModelService {

    let repository: TextModelRepositoryType

    init(repository: TextModelRepositoryType) {

        self.repository = repository
    }

    func find(completion: @escaping (Result<TextModel>) -> ()) {

        repository.find(completion: completion)
    }
}

// Repository Layer

protocol TextModelRepositoryType {

    func find(completion: @escaping (Result<TextModel>) -> ())
}

protocol PersonRepositoryType {

    func findPerson(completion: @escaping (Result<Person>) -> ())
}

class PersonRepository: PersonRepositoryType, TextModelRepositoryType {

    func find(completion: @escaping (Result<TextModel>) -> ()) {

        // ERROR HERE: "Cannot convert value of type '(Result<TextModel>) -> ()' to expected argument type '(Result<Person>) -> ()'"
        findPerson(completion: completion)
    }

    func findPerson(completion: @escaping (Result<Person>) -> ()) {

        let person = Person(text: "Adam")
        completion(.success(person))
    }
}

let repository = PersonRepository()
let service = TextModelService(repository: repository)
service.find { result in

    // result should be a Result<Person>
}

我不明白为什么编译器声明它'Cannot convert value of type '(Result<TextModel>) -> ()' to expected argument type '(Result<Person>) -> ()'Person肯定符合TextModel ...

有趣的是,当我删除Result类型并在完成时只传递一个未包装的类型(例如(TextModel?) -> ())时,编译器没有任何疑虑。

我是否在Swift编译器中遇到了限制?或者我错过了什么?

swift compiler-errors swift-protocols
1个回答
2
投票

因为Swift具有不变的泛型类型,即使T<A>可以转换为T<B>A也无法转换为B

这种情况下的解决方法是自己创建转换方法:

enum Result<T> {

    case success(T)
    case error

    func cast<R>() -> Result<R>? {
        switch self {
        case .error: return .error
        case .success(let t) where t is R: return .success(t as! R)
        default: return nil
        }
    }
}

extension Result where T : TextModel {
    func convertToTextModel() -> Result<TextModel> {
        switch self {
        case .error: return .error
        case .success(let t): return .success(t)
        }
    }
}

然后在find中调用这些转换方法:

func find(completion: @escaping (Result<TextModel>) -> ()) {
    findPerson(completion: { completion($0.convertToTextModel()) })
}

在来电方面:

let repository = PersonRepository()
let service = TextModelService(repository: repository)
service.find { result in
    if let person: Result<Person> = result.cast() {
        // ...
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.