Reputation: 769
Could you please tell, why one code does work ok and another doesn't?
This code works ok:
typealias StyleClosure<T: UIView> = (T) -> ()
func +<T>(lhs: @escaping StyleClosure<T>,
rhs: @escaping StyleClosure<T>) -> StyleClosure<T> {
return { (value: T) -> Void in
lhs(value)
rhs(value)
}
}
let s1: StyleClosure<UIView> = { (view: UIView) in
view.layer.cornerRadius = 1
}
let s2: StyleClosure<UILabel> = { (view: UILabel) in
view.font = UIFont.systemFont(ofSize: 14)
}
let s3 = s1 + s2
s3 is closure, I can pass UILabel to it. And func + can accept two closures, containing different types - UIView and UILabel.
But the following code gives an error:
class Styler<T: UIView> {
private let closure: (T) -> ()
init(_ closure: @escaping (T) -> ()) {
self.closure = closure
}
func apply(_ view: T) {
self.closure(view)
}
}
func +<T>(lhs: Styler<T>, rhs: Styler<T>) -> Styler<T> {
return Styler { (value: T) in
lhs.apply(value)
rhs.apply(value)
}
}
let styler1: Styler<UILabel> = Styler { (label: UILabel) -> Void in
label.backgroundColor = UIColor.red
}
let styler2: Styler<UIView> = Styler { (view: UIView) -> Void in
view.backgroundColor = UIColor.green
}
let styler3 = styler1 + styler2
This code gives following compile error:
Cannot convert value of type 'Styler<UIView>' to expected argument type 'Styler<UILabel>'
I kind of understand, why the second code gives an error. Do you have any idea, why first code gives no errors?
Upvotes: 2
Views: 249
Reputation: 54745
You are running into the issue of Swift generic coercion misunderstanding. Generic types in Swift are invariant, which means that Styler<A>
and Styler<B>
are completely unrelated types even if A
and B
are related (subclasses for instance).
This is why Style<UILabel>
and Styler<UIView>
are unrelated. However, closures (and hence functions) are variant (as explained here) - covariant on the return type, and contravariant on the parameter types, this is why your first example works.
Because of this, you can pass a UILabel
to a Styler<UIView>.apply
, since that is a simple function call, which accepts subclasses of the declared input argument type.
let styler1: Styler<UIView> = Styler { (label: UIView) -> Void in
label.backgroundColor = UIColor.red
}
let styler2: Styler<UIView> = Styler { (view: UIView) -> Void in
view.backgroundColor = UIColor.green
}
let styler3 = styler1 + styler2
styler1.apply(UILabel())
Upvotes: 5