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 returns
Selfand 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
Configurable
protocol that accepts yourData
associatedtype
as an argument, and has a non-static configure function:Provide a default implementation of that initializer in an extension for the
Configurable
protocol (forUIView
and its subclasses):Finally, add conformance to the protocol via an extension to any
UIView
subclasses you're interested in. All you need to do here is to implement thetypealias
andconfigure
method:}
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
UILabel
would inherit thetypealias
meaning that the compiler shouldn't have a problem inferring bothSelf
andData
, but apparently this isn't supported yet.Edit: @Cristik makes a good point about
UICollectionView
in the comments.This problem can be solved by adding a protocol extension for
Configurable
where theSelf
isUICollectionView
, using the appropriate initializer:Then, when adding conformance to
Configurable
forUICollectionView
, we make theData
typealias
aUICollectionViewLayout
:Personally, I think this is a reasonable approach for classes where the
init(frame:)
initializer isn't appropriate.