Проблема с протоколами Swift, ассоциированными типами, Self и реализациями по умолчанию

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

//protocol definition
protocol Configurable {
    associatedtype Data
    func configure(data: Data)

    static func generateObject() -> Self
}

//default implementation for any UIView
extension Configurable where Self: UIView {
    static func generateObject() -> Self {
        return Self()
    }
}

//implement protocol for UILabels
extension UILabel: Configurable {
    typealias Data = Int

    func configure(data: Int) {
        label.text = "\(data)"
    }
}

//use the protocol
let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!)  //5

У меня есть протокол, реализация по умолчанию для некоторых методов для UIView и конкретная реализация для UILabel.

Моя проблема - последняя часть... фактическое использование всей этой функциональности

let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!)  //5

Я делаю generateObject() с последующим configure(data: <something>) постоянно. Поэтому я попытался сделать следующее:

добавлять static func generateObjectAndConfigure(data: Data) -> Self к протоколу. Проблема возникает, когда я пытаюсь сделать реализацию по умолчанию для UIView для этого метода. Я получаю следующую ошибку

Method 'generateObjectAndConfigure(data:)' in non-final class 'UILabel' cannot be implemented in a protocol extension because it returnsсамоand has associated type requirements

В принципе, я не могу иметь метод, который возвращает Self и использует связанный тип. Мне действительно неприятно всегда вызывать два метода подряд. Я хочу только объявить configure(Data) за каждый класс и получить generateObjectAndConfigure(Data) бесплатно.

Какие-либо предложения?

1 ответ

Решение

Вы немного усложняете это, используя Self,

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

protocol Configurable {
    associatedtype Data
    init(data: Data)
    func configure(data: Data)
}

Обеспечить реализацию по умолчанию этого инициализатора в расширении для Configurable протокол (для UIView и его подклассы):

extension Configurable where Self: UIView {
    init(data: Data) {
        self.init(frame: CGRect.zero)
        self.configure(data: data)
    }
}

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

extension UILabel: Configurable {
typealias Data = Int
func configure(data: Data) {
    text = "\(data)"
}

}

extension UIImageView: Configurable {
    typealias Data = String
    func configure(data: Data) {
        image = UIImage(named: data)
    }
}

Эта реализация имеет дополнительный бонус: вместо статического метода вы используете инициализатор для создания ваших представлений (стандартный шаблон Swift для создания экземпляра объекта):

let label = UILabel(data: 10)
let imageView = UIImageView(data: "screenshot")

Мне не совсем понятно, почему компилятору не нравится ваша версия. Я бы подумал, что подклассы UILabel унаследует typealias Это означает, что у компилятора не должно быть проблем с выводом Self а также Data, но, видимо, это еще не поддерживается.

Редактировать: @Cristik делает хорошее замечание о UICollectionView в комментариях.

Эту проблему можно решить, добавив расширение протокола для Configurable где Self является UICollectionView, используя соответствующий инициализатор:

extension Configurable where Self: UICollectionView {
    init(data: Data) {
        self.init(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
        self.configure(data: data)
    }
}

Затем при добавлении соответствия Configurable за UICollectionViewмы делаем Datatypealias UICollectionViewLayout:

extension UICollectionView: Configurable {
    typealias Data = UICollectionViewLayout
    func configure(data: Data) {
        collectionViewLayout = data
    }
}

Лично я думаю, что это разумный подход для классов, где init(frame:) инициализатор не подходит.

Другие вопросы по тегам