19

I try to implement object oriented code by using a generic protocol. Lets say I have two protocols

protocol Executable: class {
    func execute()
}

protocol Dockable: class {
    associatedtype T
    func dock(object: T)
}

I've implemented a decorator for Executable:

final class DockableExecutable: Executable, Dockable {
    typealias T = Executable
    private let decorated: Executable
    private var docked: Executable?
    init(_ decorated: Executable) {
        self.decorated = decorated
    }
    // from Executable
    func execute() {
        decorated.execute()
        docked?.execute()
    }
    // from Dockable
    func dock(object: Executable) {
        docked = object
    }
}

Now I wan't to use it in a class like that:

final class MyViewController: UIViewController {
    init(save: Executable, uiUpdateConnector: Dockable<Executable>) {}
}

But that isn't possible because the protocol itself is not generic, only the function. The compiler tells me:

Cannot specialize non-generic type 'Dockable'

The idea is to use it like that:

let dockableExecutable = DockableExecutable(
    SQLUpdateExecutable(/** dependencies **/)
)
let controller = MyViewController(save: dockableExecutable, uiUpdateConnector: dockableExecutable)

How is the correct syntax in Swift 3 to make the compiler happy?

UPDATE 1

I made some progress with following code:

final class MyViewController: UIViewController {
    init<DOCKABLE: Dockable>(save: Executable, uiUpdateConnector: DOCKABLE) where DOCKABLE.T: Executable {}
}

It looks strange, maybe someone has a better idea? When using the class now I get:

Generic parameter 'DOCKABLE' could not be inferred

So my questions hasn't changed:

How is the correct syntax in Swift 3 to make the compiler happy?

Update 2

It seems it isn't possible to use the swift generic (or better: type associated) protocols in protocol based object oriented programming.

So we have to wrap them into some kind of container and loose the protocol based approach or we have to define different protocols for each situation.

Because working without protocols is no option for me, I have to write different protocols without generics. Shame on swift

ObjectAlchemist
  • 1,109
  • 1
  • 9
  • 18
  • 1
    Take a look at this to see if it gives you an insight: [How to create generic protocols in Swift? - Stack Overflow](http://stackoverflow.com/questions/24469913/how-to-create-generic-protocols-in-swift) – leanne Mar 19 '17 at 14:02
  • Thanks for the link, @leanne. But I think it's a different situation. I already managed to derive from a generic protocol (see the DockableExecutable implementation above). Now I try to use the protocol. – ObjectAlchemist Mar 19 '17 at 14:08
  • Your link describes the solution I wrote in my Update. Therefore it's a part of the solution I think. (vote up) – ObjectAlchemist Mar 19 '17 at 14:19
  • Another link that may give you insight: [ios - Generics in Swift - "Generic parameter 'T' could not be inferred - Stack Overflow](http://stackoverflow.com/questions/38999102/generics-in-swift-generic-parameter-t-could-not-be-inferred) - your update looks similar; however, the OP there has added the protocol to the view controller's class signature, or in your case: `final class MyViewController: UIViewController, Dockable {` – leanne Mar 19 '17 at 16:40
  • Thanks for the link again. It gave me some really good insights how swift generics work. As i already wrote in the update, my conclusion is that type associated protocols are unusable for object oriented programmers like me. – ObjectAlchemist Mar 19 '17 at 17:40
  • 1
    The problem you're running into is that [protocols don't conform to themselves](http://stackoverflow.com/q/33112559/2976878) – so you cannot use `Executable` as a type that conforms to `Executable`. You could change your `where` clause to `where DOCKABLE.T == Executable`, but then that would only accept `Dockable` types where `T` *is* `Executable` (not a `T` that conforms to `Executable`). – Hamish Mar 19 '17 at 17:48
  • Thanx for another piece of information @Hamish! I tried to change the where to == and it compiles, but I'm unable to assign it to a private var. Also: when debugging, even the debug area can't tell me the type. Printing it leads to an error. This is really bad. – ObjectAlchemist Mar 19 '17 at 18:13

2 Answers2

5

I'm not sure that your problem can be solved without type erasure, because you cannot use protocols with associated types for types of variables or parameters of functions.

Try the solution with type erasure:

final class AnyDockable<U>: Dockable {
    typealias T = U

    let _dock: (U) -> Void
    let _execute: () -> Void

    init<Base: Dockable & Executable>(base: Base) where Base.T == U {
        _dock = base.dock
        _execute = base.execute
    }

    func dock(object: U) {
        _dock(object)
    }

    func execute() {
        _execute()
    }
}

Your compiler will be happy, I've checked it:

class Exe: Executable {
    func execute() {
        print("")
    }
}
let executable: Executable = Exe()

let de = AnyDockable(base: DockableExecutable(executable))

final class MyViewController: UIViewController {

    init(save: Executable, uiUpdateConnector: AnyDockable<Executable>) {
        super.init(nibName: "", bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

MyViewController(save: executable, uiUpdateConnector: de)
ixrevo
  • 96
  • 3
  • 1
    I have to experiment a little bit, until I can tell you if it helps me. In your current implementation the parameter uiUpdateConnector is no longer a protocol, it's the concrete class AnyDockable. Thats a violation of my protocol based object oriented approach. The AnyDockable is also very specifically linked to Executable. So if I write a DockableStringValue / DockableWhatever / .. I also have to implement a AnyDockable like class for all of them. That breaks the idea of Generics. The generic Dockable interface doesn't make any sense than. Don't you think? – ObjectAlchemist Mar 19 '17 at 16:01
  • I think you are right. It isn't possible :-/ - "vote up" for this part of the answer, but I will wait a little bit with accepting it as answer. Maybe some genius tell us we are wrong ;-) – ObjectAlchemist Mar 19 '17 at 17:32
  • Yes, you are right. AnyDockable should be named AnyDockableExecutable. I don't think that type erased AnyDockableExecutable violates protocol based design, because it acts like a protocol: all implementation details are still hidden. Actually Swift Standard library uses this pattern http://swiftdoc.org/v3.1/type/AnySequence/ – ixrevo Mar 19 '17 at 19:11
  • Does `uiUpdateConnector` have to conform to Executable protocol? If it doesn't then it's possible to remove Executable part from AnyDockable and it will act like generic type (String, whatever). – ixrevo Mar 19 '17 at 19:17
1

If I am not mistaken you are looking for Protocol Composition (multi-protocol conformance), no? You could then use different of your decorators afterwards.

Maybe you are looking for this: Swift 3 protocol<A, B>, and in Swift 4 A & B:

final class MyViewController: UIViewController {
    init(save: Executable, uiUpdateConnector: protocol<Dockable, Executable>) {}
}

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html

http://braking.github.io/require-conformance-to-multiple-protocols/

Multiple Type Constraints in Swift

smat88dd
  • 2,258
  • 2
  • 25
  • 38