Search code examples
swiftprotocolscomposition

Procotol composition on the same property


Is there a way to achieve this ?

protocol VCProtocol1: UIViewController {
    var viewModel: VMProtocol1? { get set }
}

protocol VCProtocol2: UIViewController {
    var viewModel: VMProtocol2? { get set }
}

class VC: UIViewController, VCProtocol1, VCProtocol2 {
    var viewModel: (VMProtocol1 & VMProtocol2)?
}

What I want to do is composition on ViewController to avoid re-implementing the same code in multiple ViewControllers.

Edit:

To be more precise, the problem here is I want my viewModel property to be shared between both protocols because ultimately I want to implement something like this:

- View Model

protocol VMProtocol1 {
    func vmTest1()
}

protocol VMProtocol2 {
    func vmTest2()
}

class ViewModel: VMProtocol1, VMProtocol2 {
    func vmTest1() { }
    func vmTest2() { }
}

- View Controller

protocol VCProtocol1: UIViewController {
    var viewModel: VMProtocol1 { get set }

    func vcTest1()
}

extension VCProtocol1 {
    func vcTest1() {
        // This is the key point, I want to be able to refer to my viewModel property internally in each protocol.
        self.viewModel.vmTest1() 
    }
}

protocol VCProtocol2: UIViewController {
    var viewModel: VMProtocol2 { get set }
}

class VC: UIViewController, VCProtocol1, VCProtocol2 {
    var viewModel: (VMProtocol1 & VMProtocol2)?
}

Solution

  • This is actually not possible. One entity that implements multiple protocols which declare the same property (here viewModel) but with a different type is simply not possible in Swift.

    The closest you can do is to use a protocol which define the viewModel type as an associated type and provide conditional extensions (it will force you to use a generic implementation).

    Please note that this is a complex implementation that require a good knowledge of generic protocols. I would not recommend using a such complicated design without understanding exactly what you are doing.

    View model

    protocol VMProtocol1 {
        func vmTest1()
    }
    
    protocol VMProtocol2 {
        func vmTest2()
    }
    
    class ViewModel: VMProtocol1, VMProtocol2 {
        func vmTest1() { }
        func vmTest2() { }
    }
    

    Implementation

    protocol VCProtocolBase {
        associatedtype ViewModel
        var viewModel: ViewModel { get }
    }
    
    protocol VCProtocol1: VCProtocolBase {
        func vmTest1()
    }
    
    protocol VCProtocol2: VCProtocolBase {
        func vmTest2()
    }
    
    extension VCProtocolBase where Self: VCProtocol1, Self.ViewModel: VMProtocol1 {
        func vmTest1() {
            viewModel.vmTest1()
        }
    }
    
    extension VCProtocolBase where Self: VCProtocol2, Self.ViewModel: VMProtocol2 {
        func vmTest2() {
            viewModel.vmTest2()
        }
    }
    
    
    final class VC<ViewModel: VMProtocol1 & VMProtocol2>: VCProtocolBase, VCProtocol1, VCProtocol2 {
        var viewModel: ViewModel
    
        init(viewModel: ViewModel) {
            self.viewModel = viewModel
        }
    }
    
    VC(viewModel: ViewModel()).vmTest1()
    VC(viewModel: ViewModel()).vmTest2()
    

    The idea here is to rely on generics and conditional extensions to provide the default implementations for the VCProtocolX, which is the role of VCProtocolBase.