Проблема с протоколами 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
протокол, который принимает ваш 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
UICollectionViewLayout
:
extension UICollectionView: Configurable {
typealias Data = UICollectionViewLayout
func configure(data: Data) {
collectionViewLayout = data
}
}
Лично я думаю, что это разумный подход для классов, где init(frame:)
инициализатор не подходит.