I can't get the following code to work:
@objc protocol Child { }
@objc protocol Parent {
var child: Child { get }
}
class ChildImpl: Child {
// not part of the `Child` protocol
// just something specific to this class
func doSomething() { }
}
class ParentImpl: Parent {
let child = ChildImpl()
func doSomething() {
// need to be able to access `doSomething`
// from the ChildImpl class
childImpl.doSomething()
}
// this would solve the problem, however can't access the ChildImpl members
// that are not part of the protocol
// let child: Child = ChildImpl()
// as well as this, however maintaining two properties is an ugly hack
// var child: Child { return childImpl }
// private let childImpl = ChildImpl()
}
The error I get:
Type 'ParentImpl' does not conform to protocol 'Parent'.
Do you want to add protocol stubs?
Basically I have two parent-child protocols, and two classes that implement the two protocols. But still, the compiler doesn't recognize that that ChildImpl
is a Child
.
I can make the errors go away if I use an associated type on Parent
protocol Parent {
associatedtype ChildType: Child
var child: ChildType { get }
}
, however I need to have the protocols available to Objective-C, and also need to be able to reference child
as the actual concrete type.
Is there a solution to this that doesn't involve rewriting the protocols in Objective-C
, or doesn't add duplicate property declarations just to avoid the problem?
I referred in the comments a link showing what you've tried, using associated type or separate property just to fulfil protocol conformance. I thing Swift will soon support inferring type from composed types like let child: Child & ChildImpl = ChildImpl()
or simply child: ChildImpl
since ChildImpl
is Child
. But until then thought I suggest one more alternative which is to separate the apis you need from ChildImpl
and put them in a separate protocol to which Child
inherits. This way when Swift compiler supports this feature, you don't need to refactor but simply remove it.
// TODO: Remove when Swift keeps up.
@objc protocol ChildTemporaryBase { }
private extension ChildTemporaryBase {
func doSomething() {
(self as? ChildImpl).doSomething()
}
}
@objc protocol Child: ChildTemporaryBase { }
class ParentImpl: Parent {
let child: Child = ChildImpl()
func testApi() {
child.doSomething?()
}
}
What you're trying to do is called covariance
and swift does not support covariance in protocols or classes/structs conforming to those protocols.
You either have to use Type-Erassure
, or associatedTypes
:
protocol Child { }
protocol Parent {
associatedtype ChildType: Child
var child: ChildType { get }
}
class ChildImpl: Child {
func doSomething() {
print("doSomething")
}
}
class ParentImpl: Parent {
typealias ChildType = ChildImpl
let child = ChildImpl()
func test() {
child.doSomething()
}
}
ParentImpl().test() // will print "doSomething"
And here's the Type-Erased Parent for general usage of Parent
protocol:
struct AnyParent<C: Child>: Parent {
private let _child: () -> C
init <P: Parent>(_ _selfie: P) where P.ChildType == C {
let selfie = _selfie
_child = { selfie.child }
}
var child: C {
return _child()
}
}
let parent: AnyParent<ChildImpl> = AnyParent(ParentImpl())
parent.child.doSomething() // and here in Parent protocol level, knows what is child's type and YES, this line will also print "doSomething"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With