9

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?

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • Related: [Swift Protocol inheritance and protocol conformence issue](http://stackoverflow.com/questions/40410884/swift-protocol-inheritance-and-protocol-conformence-issue) (see also: [Protocol doesn't conform to itself?](http://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself)). – dfrib Jan 03 '17 at 11:52
  • See [this Q&A](http://stackoverflow.com/q/42561685/2976878) – one feasible (but not particularly nice) solution in your case would be to define a dummy property of type `Child!` to `ParentImpl` to satisfy the protocol requirement (and then have your actual property be of type `ChildImpl!`). – Hamish Mar 10 '17 at 11:13
  • @Hamish, I evaluated that approach also, however (as you said) it's not very nice, and it requires maintaining two properties with the same role :( – Cristik Mar 10 '17 at 11:25
  • @Cristik Yeah :/ Unfortunately, I think it's probably the best you're going to be able to manage until Swift supports it – although I hope someone can prove me wrong with a better workaround. – Hamish Mar 10 '17 at 11:28
  • I find this to be a bug in Swift's compiler tbh – Mihai Fratu Feb 22 '18 at 17:53
  • Can't do that in Swift 4 yet: https://stackoverflow.com/a/42716340/3405387 – Lukas Feb 25 '18 at 18:03
  • @Lukas this situation is slightly different than the referenced one, here we're dealing with a concrete type for the `child` property. – Cristik Feb 25 '18 at 18:46
  • @Cristik It is identical, look at the question. The answer happens to suggest using an associated type or a dummy computed property for the sake of fulfilling the protocol conformance which you called `ugly hack` which I agree btw. – Lukas Feb 25 '18 at 19:46
  • @Lukas hmm... you might be right, the problems are similar – Cristik Feb 25 '18 at 20:36

6 Answers6

2

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?()
    }
}
Lukas
  • 3,423
  • 2
  • 14
  • 26
  • This makes `doSomething()` public, and I want to keep this internal to the cluster of two classes. That's why in my code `doSomething` is part of the implementation class. – Cristik Feb 26 '18 at 10:36
  • @Cristik Maybe you can add the api in `ChildTemporaryBase` private extension. I edited my answer with something like that, this is definitely more code than using the computed property but but I guess it keeps `ParentImpl` clean. – Lukas Feb 26 '18 at 13:03
  • Interesting approach, however seems I'll have to add a lot of boilerplate code, and things complicate when the methods return values, as I'll need to either `guard` or force unwrap the result – Cristik Feb 26 '18 at 13:48
2

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"
farzadshbfn
  • 2,710
  • 1
  • 18
  • 38
  • Unfortunately I can't use associated values, nor type erasers with generics, as I need to interface with Objective-C... – Cristik Feb 26 '18 at 13:50
  • @Cristik This topic is actually very old and have been discussed on many blogs and other SO questions. If objc interface is important to you, you have no other choice, bear with the ugly solution :(. I had to use same mechanism for a project and to be honest it's not "that" bad, but well, you'll always notice the fact that man this code annoys me! Things like this, is one of the reasons I won't recruit or work in a company that still codes in Obj-c. If you search for `Swift protocol covariance` all topics will pop-up. – farzadshbfn Feb 26 '18 at 15:26
  • Also, if possible you can define `Child` and `Parent` as Abstract classes. IK swift does not have such term, but you can use some work arounds like `fatalError("implement in subclass") or things like that. – farzadshbfn Feb 26 '18 at 15:30
  • Thanks for the suggestions, I'm aware of all kind of workarounds, however I was aiming for implementing this as clean as possible. Too bad that the Swift compiler can't properly infer a type that is clearly present at that location... – Cristik Feb 26 '18 at 15:32
0

If you don't mind adding extension property to ParentImpl class:

@objc protocol Child {}

@objc protocol Parent {
    var child: Child! { get }
}

class ChildImpl: Child { }

class ParentImpl: Parent {
    var child: Child!
}

extension ParentImpl {

    convenience init(child: Child?) {
        self.init()
        self.child = child
    }

    var childImpl: ChildImpl! {
        get { return child as? ChildImpl }
        set { child = newValue }
    }

}

let parent = ParentImpl(child: ChildImpl())
let child = parent.child
bubuxu
  • 2,187
  • 18
  • 23
  • @Cristik Just a thought about how to implement your idea, not actually solving your problem. – bubuxu Jan 03 '17 at 12:16
  • Thanks for your input, unfortunately generics are also unavailable in objective-c... – Cristik Jan 03 '17 at 12:26
  • Remove the generics now. – bubuxu Jan 03 '17 at 23:15
  • Sorry for the really late reply, this solution is still not optimal as it uses IUO's, which are not recommended to be used, and needs an extra almost-duplicate property, which I want to avoid. – Cristik Dec 22 '17 at 12:35
0

Your ParentImpl class hasn't Child type protocol. I solved that this solution.

class ParentImpl: Parent {
   var child: Child = ChildImpl()
}
0

One almost-good solution that I found, now that we have Swift 5.1, is by using a property wrapper:

@propertyWrapper
struct Hider<T, U> {
    let wrappedValue: T

    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }

    var projectedValue: U { return wrappedValue as! U }
}

@objcMembers class ParentImpl: NSObject, Parent {
    @Hider<Child, ChildImpl> var child = ChildImpl()
}

This way child is exposed as Child, and $child is exposed as ChildImpl, which allows usage of non-protocol members from within ParentImpl.

The solution is not ideal, as I could not yet find a way to describe that T should be a super-type for U.

Cristik
  • 30,989
  • 25
  • 91
  • 127
0

Short answer is NO, the current design doesn't work with the current Swift language.

The reason is that the var child: Child { get } protocol requirement creates behind the scenes an existential container, something like ExistentialContainer<Child>. And due to this, any type conforming to the protocol must also declare an existential container of the same type, and not ExistentialContainer<Subtype>.

Seems that existential containers are not covariant, so for the time being one needs to use the workarounds described in the question and the other answers.

Cristik
  • 30,989
  • 25
  • 91
  • 127