Associatedtype usage in protocols

Hi -
pardon if I miss a pretty basic point. I would like to implement something in Swift like (invalid syntax - "Protocols do not allow generic parameters")

    import Foundation 

    class SomeModel {}
    protocol EditingController<ModelType> : class {
        var model : ModelType! { get set }
    }

    class SomeModel {}

    class SomeController : UIViewController, EditingController<SomeModel> {
        var model : SomeModel!

       // bunch of generic functions based on ModelType  and: 
        override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
            if let c = segue.destinationController as? 
                EditingController<SomeModel> {
                c.model = self.model
            }
        }
    }

and fail to get the semantics right - my naive idea was something like the following should work. How can I express "I need a EditingController whose ModelType == the current ModelType"?

    import Foundation 
    class SomeModel {}

    protocol EditingControllerAT : class {
        associatedtype ModelType
        var model : ModelType! { get set }
    }

    class SomeControllerAT : NSViewController, EditingControllerAT {
        var model : SomeModel!

        override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
            if let c = segue.destinationController as? 
                 (EditingControllerAT where ModelType == SomeModel.self) {
                c.model = self.model
            }
        }
    }

Thanks!

Hello !

       if let c = segue.destinationController as?
          EditingControllerAT, type(of: c.model) == SomeModel {
            c.model = self.model
        }

This should do the trick (I haven't tested it though).

I used a similar setup in one of my iOS projects, so everything down here is written with 0 AppKit-specific knowledge :sweat_smile:

@TypeSwiftApostle's solution won't work because of this famous compiler error: Protocol 'EditingControllerAT' can only be used as a generic constraint because it has Self or associated type requirements. This error tells us that we have to use somewhere a generic type variable constrained to EditingControllerAT and cast to that, instead of casting directly to the protocol's existential type.
Being able to cast to PATs (Protocols with associatedtypes) is part of a set of improvements to the language generally referred at as "generalized existentials", not yet in the language.

What you're able to do today is something like this:

import Foundation
import AppKit

class SomeModelA {}

protocol HasModel: class {
    associatedtype Model
    var model: Model! { get set }
}

class ModelPassingSegue<VCSource: HasModel, VCDestination: HasModel>: NSStoryboardSegue
    where VCSource.Model == VCDestination.Model {

    override func perform() {
        guard let source = self.sourceController as? VCSource else {
            assertionFailure("\(sourceController) is of type \(type(of: sourceController)) instead of \(VCSource.self)")
            return
        }

        guard let destination = self.destinationController as? VCDestination else {
            assertionFailure("\(destinationController) is of type \(type(of: destinationController)) instead of \(VCDestination.self)")
            return
        }

        destination.model = source.model
        super.perform()
    }
}

class ControllerA: NSViewController, HasModel {
    var model: SomeModelA!
}

class ControllerB: NSViewController, HasModel {
    var model: SomeModelA!
}

class SegueFromAtoB: ModelPassingSegue<ControllerA, ControllerB> {}

I made a generic subclass of NSStoryboardSegue, and constrained its type parameters to the HasModel protocol (your "EditingControllerAT" renamed). A ugly detail is that since Storyboards do not support setting generic classes to views, you cannot assign ModelPassingSegue<ControllerA, ControllerB> to your segue class, and you have to create a concrete subclass from the generic class for each different pair of VCs (in this case SegueFromAtoB ).

2 Likes