Не удается преобразовать значение типа с совпадающими типами - я столкнулся с ограничением компилятора? - PullRequest
3 голосов
/ 29 марта 2019

Фон:

Я создаю приложение со служебным слоем, который должен возвращать модели, которые похожи (например, у всех есть свойство text: String, определенное в протоколе TextModel).Служба содержит репозиторий, который находит и возвращает модели конкретного типа, соответствующие TextModel.Хранилище должно хранить конкретную информацию о типах для внутренних работ.Я хочу оставить сервис свободным от конкретного типа модели, поэтому мне не нужно повторять его для каждого типа модели.Компилятор не позволяет мне сделать это ...

Проблема:

Следующий код, который я упростил на игровой площадке, не скомпилируется:

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>
}

Iне понимаю, почему компилятор заявляет, что 'Cannot convert value of type '(Result<TextModel>) -> ()' to expected argument type '(Result<Person>) -> ()', когда Person определенно соответствует TextModel ...

Интересно также то, что когда я удаляю тип Result и просто передаю развернутыйвведите завершение (например, (TextModel?) -> ()), компилятор не имеет префиксов.

Я столкнулся с ограничением в компиляторе Swift?Или я что-то упустил?

1 Ответ

2 голосов
/ 29 марта 2019

Поскольку 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() {
        // ...
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...