Почему в фабричном методе универсальных шаблонов Swift предупреждение компилятора Generi c, класс 'X' требует, чтобы 'Object' соответствовал 'Y' - PullRequest
1 голос
/ 05 февраля 2020

В качестве обзора я пытаюсь реализовать слой абстракции данных в Swift. Я использую два SDK базы данных, но пытаюсь изолировать их специфические c API от остальной системы.

Я пытаюсь реализовать фабричный шаблон, который будет возвращать правильный Объект основан на соответствии протокола поставляемого конкретного типа. Но компилятор выдает мне красные флажки, которые я не могу обернуть.

Класс Thing - это объект первого класса, который свободно перемещается по уровням logi c и UI приложения, и некоторые объекты сохраняются в хранилище Realm (что не должно иметь значения) и имеют определенный тип c, который является подклассом объекта Realm. Этот тип возвращается функцией stati c, которая должна быть там, чтобы соответствовать протоколу FromDataSourceA.

protocol FromDataSourceA {
    static func A_objectType () -> A_Object.Type
}

class MyObject {
    ...
}

class Thing: MyObject, FromDataSourceA {
    static func A_objectType () -> A_Thing.Type
    ...
}

// Example usage
let target = DataTarget<Thing>(url: "url")
let dataSource = target.dataSourceBuilder()

Как видите, конкретный тип Thing соответствует FromDataSourceA и является передан в инициализатор DataTarget. DataSourceBuilder должен иметь возможность проверить, что Thing соответствует FromDataSourceA, чтобы иметь возможность вернуть правильный DataSource.

Класс DataSourceTypeA выглядит как

class DataSourceTypeA<T: MyObject & FromDataSourceA>: DataSource<T> {
    let db: DatabaseTypeA // All db-specific APIs contained here

    override init (target: DataTarget<T>) {
        self.db = try! DatabaseTypeA()
        super.init(target: target)
    }

    override func create<T: MyObject & FromDataSourceA> (object: T) {
        db.beginWrite()
        db.create(T.A_objectType(), value: object.dict()) // <-- T.A_objectType() is what the conformance to FromDataSourceA is needed for
        try! db.commitWrite()
    }

    override func get<T: MyObject & FromDataSourceA>(id: Int) -> T {
        ...
    }
}

class DataSource<T>: AnyDataSource {
    let target: DataTarget<T>

    init (target: DataTarget<T>) {
        self.target = target
    }

    func create<T> (object: T) { }

    func get<T>(id: Int) -> T { fatalError("get(id:) has not been implemented") }
}

protocol AnyDataSource {
    func create<T> (object: T)
    func get<T> (id: Int) -> T
}

Проблема, с которой я сейчас сталкиваюсь, заключается в том, что когда я проверяю, что метатип соответствует FromDataSourceA, компилятор выдает мне это предупреждение

class DataTarget<T: MyObject> {
    ...

    func dataSourceBuilder () -> DataSource<T> {
        if T.self is FromDataSourceA { // <-- Thing.self conforms to FromDataSourceA

            // The Problem:

            return DataSourceTypeA(target: self) // Generic class 'DataSourceTypeA' requires that 'MyObject' conform to 'FromDataSourceA'

        } else {
            ...
        }
    }
}

Почему компилятор не разрешает мне возвращать экземпляр DataSourceTypeA с аргументом self если шаблон c T проходит условное утверждение и доказано, что он соответствует FromDataSourceA?

1 Ответ

1 голос
/ 05 февраля 2020

Проблема в том, что вызов

return DataSourceTypeA(target: self)

разрешается во время компиляции, поэтому он не помогает проверять

if T.self is FromDataSourceA { }

во время выполнения. Возможное решение состоит в том, чтобы сделать проверку соответствия проверкой во время компиляции, определив ограниченное расширение:

extension DataTarget where T: FromDataSourceA {
    func dataSourceBuilder() -> DataSource<T> {
        return DataSourceTypeA(target: self)
    }
}

Если необходимо, вы можете добавить больше реализаций для других ограничений:

extension DataTarget where T: FromDataSourceB {
    func dataSourceBuilder() -> DataSource<T> {
        // ...
    }
}

или добавьте реализацию по умолчанию:

extension DataTarget {
    func dataSourceBuilder() -> DataSource<T> {
        // ...
    }
}

Для вызова

let dataSource = target.dataSourceBuilder()

компилятор выберет наиболее конкретную c реализацию, в зависимости от состояния c (время компиляции ) информация о типе target.

...