I am trying to get some functionality through default implementations that I can't nail. Consider the following code, which is a simplification of what I'm trying to do, but captures the problem as simply as possible.
//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
I have a protocol, a default implementation for some methods for UIView, and the a specific implementation for UILabel.
My issue is the last part... the actual use of all this functionality
let label = UILabel.generateObject()
label.configure(data: 5)
print(label.text!) //5
I find myself doing generateObject() followed by configure(data: <something>) constantly. So I tried doing the following:
Add static func generateObjectAndConfigure(data: Data) -> Self to the protocol. The issue comes when I try to make a default implementation for UIView for this method. I get the following error
Method 'generateObjectAndConfigure(data:)' in non-final class 'UILabel' cannot be implemented in a protocol extension because it returnsSelfand has associated type requirements
Basically, I can't have a method that returns Self and uses an associated type. It feels really nasty for me to always call the two methods in a row. I want to only declare configure(Data) for each class and get generateObjectAndConfigure(Data) for free.
Any suggestions?
You're overcomplicating this a bit, by using
Self.All you need to do is declare an initialiser in your
Configurableprotocol that accepts yourDataassociatedtypeas an argument, and has a non-static configure function:Provide a default implementation of that initializer in an extension for the
Configurableprotocol (forUIViewand its subclasses):Finally, add conformance to the protocol via an extension to any
UIViewsubclasses you're interested in. All you need to do here is to implement thetypealiasandconfiguremethod:}
This implementation has the added bonus that you're using an initializer to create your views (the standard Swift pattern for instantiating an object), rather than a static method:
It's not exactly clear to me why the compiler doesn't like your version. I would have thought that subclasses of
UILabelwould inherit thetypealiasmeaning that the compiler shouldn't have a problem inferring bothSelfandData, but apparently this isn't supported yet.Edit: @Cristik makes a good point about
UICollectionViewin the comments.This problem can be solved by adding a protocol extension for
Configurablewhere theSelfisUICollectionView, using the appropriate initializer:Then, when adding conformance to
ConfigurableforUICollectionView, we make theDatatypealiasaUICollectionViewLayout:Personally, I think this is a reasonable approach for classes where the
init(frame:)initializer isn't appropriate.