0

I'm working my way into the injection dependency issue. Now the question arises how to use the interface segregation principle in the case of the delegate pattern? I am using the Swinject framework for dependency injection. How can I solve this?

class Client {
    private var interface: ParentInterface

    ...

    func execute() {
        interface = globalContainer.resolve(ParentInterface.self)
        interface?.parentMethod()
    }
}
protocol ParentInterface {
    func parentMethod()
}

class Parent: ParentInterface, ParentDelegate {

    // Dependency
    private var child: Child? //  I WANT TO USE AN PROTOCOL HERE, NOT THE CLASS

    init(childReference: Child) {
        self.child = childReference
        self.child?.delegate = self //  But if I use the protocol I cant access the delegate property
    }

    public func parentMethod() {
        let result = calcSomething()

        // Access child class interface method
        child?.childMethod(result)
    }
    ...
}

The child class, nothing unusual so far.

protocol ParentDelegate: class {
    func delagteMethod(value: Double)
}

protocol ChildInterface {
    func childMethod(_ result: Double)
}

class Child: ChildInterface {

    weak var delegate: ParentDelegate?

    ...

    private func delagteMethod() {
      delegate?.delagteMethod(value: someValue)
    }

}

But to inject the dependencies properly I need a protocol and not a direct class reference, right? Like this:


//  Would like to
container.register(ParentInterface.self) { r in
    Parent(childInterface: r.resolve(ChildInterface.self)!)
} 

//  Only way I get it working without interface
container.register(ParentDelegate.self) { r in
    Parent(childClass: Child())
}

container.register(ChildInterface.self) { _ in Child() }
    .initCompleted { r, c in
        let child = c as! Child
        child.delegate = r.resolve(ParentDelegate.self) 
}


In short, I'm going around in circles. If I use an interface for the child class I can't access the delegate property, if I use a class reference I can't reasonably to mock/stub the interface methods.

What am I missing? Thanks a lot in advance!

Tyrus Rechs
  • 73
  • 2
  • 12

1 Answers1

1

I would recommend removing self.child?.delegate = self from the Parent constructor for two reasons:

  1. Shouldn't the DI framework be responsible for wiring all the dependencies?
  2. In Swinject specifically, this may lead to incorrect dependencies, see the remark in docs

Pattern that I tend to use is more or less what you came up with:

class Parent: ParentInterface, ParentDelegate { 
    init(child: ChilInterface) { self.child = child }
}

class Child: ChildInterface {
    weak var parent: ParentDelegate?
}

container.register(ParentInterface.self) { Parent(child: $0.resolve(ChildInterface.self)!) }
    .implements(ParentDelegate.self)

container.register(ChildInterface.self) { _ in Child() }
    .initCompleted { ($1 as! Child).delegate = $0.resolve(ParentDelegate.self) }

Additionally, if you wanted to get rid of force casting, you could do something like:

container.register(Child.self) { _ in Child() }
   .implements(ChildInterface.self)
   .initCompleted { $1.delegate = $0.resolve(ParentDelegate.self) }
Jakub Vano
  • 3,833
  • 15
  • 29