Проблема с протоколами Swift, ассоциированными типами, Self и реализациями по умолчанию - PullRequest
0 голосов
/ 03 октября 2018

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

//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 Self and has associated type requirements

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

Есть предложения?

1 Ответ

0 голосов
/ 03 октября 2018

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

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

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, мы делаем Data typealias a UICollectionViewLayout:

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

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

...