How to Use Protocol as Associatedtype in Another Protocol in Swift

Unable to use protocol as associatedtype in another protocol in Swift

The problem, which David has already alluded to, is that once you constrain a protocol's associatedtype to a specific (non @objc) protocol, you have to use a concrete type to satisfy that requirement.

This is because protocols don't conform to themselves – therefore meaning that you cannot use Address to satisfy the protocol's associated type requirement of a type that conforms to Validator, as Address is not a type that conforms to Validator.

As I demonstrate in my answer here, consider the counter-example of:

protocol Validator {
init()
}
protocol Address : Validator {}

protocol FormRepresentable {
associatedtype ValueWrapper: Validator
}

extension FormRepresentable {
static func foo() {
// if ValueWrapper were allowed to be an Address or Validator,
// what instance should we be constructing here?
// we cannot create an instance of a protocol.
print(ValueWrapper.init())
}
}

// therefore, we cannot say:
enum AddressFrom : FormRepresentable {
typealias ValueWrapper = Address
}

The simplest solution would be to ditch the Validator protocol constraint on your ValueWrapper associated type, allowing you to use an abstract type in the method argument.

protocol FormRepresentable {
associatedtype ValueWrapper
func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom : Int, FormRepresentable {

// ...

func valueForDetail(valueWrapper: Address) -> String {
// ...
}
}

If you need the associated type constraint, and each AddressFrom instance only expects a single concrete implementation of Address as an input – you could use generics in order for your AddressFrom to be initialised with a given concrete type of address to be used in your method.

protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom : Int, FormRepresentable {

// ...

func valueForDetail(valueWrapper: T) -> String {
// ...
}
}

// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom.Address1

However, if you require both the associated type constraint and each AddressFrom instance must be able to handle an input of any type of Address – you'll have use a type erasure in order to wrap an arbitrary Address in a concrete type.

protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}

struct AnyAddress : Address {

private var _base: Address

var addressLine1: String {
get {return _base.addressLine1}
set {_base.addressLine1 = newValue}
}
var country: String {
get {return _base.country}
set {_base.country = newValue}
}
var city: String {
get {return _base.city}
set {_base.city = newValue}
}

init(_ base: Address) {
_base = base
}
}

enum AddressFrom : Int, FormRepresentable {

// ...

func valueForDetail(valueWrapper: AnyAddress) -> String {
// ...
}
}

let addressFrom = AddressFrom.Address1

let address = ShippingAddress(addressLine1: "", city: "", country: "")

addressFrom.valueForDetail(AnyAddress(address))

Reuse associated type from protocol in another protocol

The requestType associated type isn't necessary in the RequestProcessor protocol, as it is implicit based on the requester associated type.

You should just be able to define the RequestProcessor protocol like this:

protocol RequestProcesser: AnyObject {
associatedtype requester: Requester
var request: requester { get set }
}

And use it like this:

class MyRequester: Requester {
typealias requestType = Int
var response: ((Int) -> ())?
}

class MyRequestProcessor: RequestProcesser {
typealias requester = MyRequester
var request = MyRequester()
}

Now, the response closure parameter inside the process() method will take on the associated type of the MyRequester protocol (in this case Int), so no casting is necessary.

Protocol inheritance with associated type

Once a protocol has an associated type, that protocol can't be used as a type by itself for instance declarations-- only for generic constraints and declaring conformance.

So in this case, Swift is saying "yeah, but what is the concrete type for StartRouterProtocol's associated type?"

In this case, it's asking you to either:

  1. Use a concrete type directly, i.e. let router: MyStartViewClass with this conformance declaration, elsewhere: class MyStartViewClass: StartRouterProtocol { ... })
  2. OR, push the need for a concrete type up one layer, as a generic constraint i.e.
class MyRouterController {
let router: T
}

This is probably not what you were hoping for, but unfortunately associated types add complexity to how you use protocols, especially if you're familiar with generics & interfaces from other languages. (i.e. Java/C# interfaces)

You can work around some aspects of associated types by using a concept called "type erasure" -- but that can cause other problems and complexity.

Here's some further reading that may help: https://medium.com/monstar-lab-bangladesh-engineering/swift-from-protocol-to-associatedtype-then-type-erasure-a4093f6a2d08

Protocol restriction on another protocol associated type

As far as I can see MyStruct? (aka Optional) and Codable? (aka
Optional) are equivalent?

No, they are not. MyStruct conforms to Codable but is not equivalent of it.

As in: every MyStruct is Codable but not every Codable is MyStruct.


You can try changing MyType == Codable? to MyType: Codable:

protocol MyProtocolCodable where Self: MyProtocol, MyType: Codable {}

as MyStruct is not equal to Codable?.

returning swift protocol associated type in multiple methods

The solution you came up with by playing around is exactly what you need

As mentioned elsewhere, the main issue with your first protocol is that you're enforcing createSomeView() createAnotherView() both return the same type. While ViewA() and ViewB() are both candidates for V, since they conform to View they are still different types, and therefore cannot BOTH be V in a given object.

By defining both V1 and V2, you allow for each function to return a different type, or the same type, it's all acceptable. By making both V1 and V2 require View conformance, you allow for the some View syntax

Use protocol with constrained associated type as property in Swift

To expand on my questions in the comments, looking at this code it looks like it would be exactly as flexible without adding AddressBookCellModelType or AddressBookViewModelType, and this would also get rid of the headaches, while still being generic over DataSourceCompatible.

// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}

// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)
struct AddressBookCellModel {
var name: String
var photo: UIImage?
var isInvited: Bool
}

// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
let sectionedContacts: [[AddressBookCellModel]] = []
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
return sectionedContacts[indexPath.section][indexPath.row]
}
}

class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModel!

func configure(viewModel: AddressBookViewModel) {
self.viewModel = viewModel
}
}

Doing it this way allows for a generic VC without introducing more pieces that required:

class DataSourceViewController: UIView {
private var viewModel: DataSource.CellModel!

func configure(viewModel: DataSource.CellModel) {
self.viewModel = viewModel
}
}

let vc = DataSourceViewController()

What does Protocol ... can only be used as a generic constraint because it has Self or associated type requirements mean?

Protocol Observing inherits from protocol Hashable, which in turn inherits from protocol Equatable. Protocol Equatable has the following requirement:

func ==(lhs: Self, rhs: Self) -> Bool

And a protocol that contains Self somewhere inside it cannot be used anywhere except in a type constraint.

Here is a similar question.

Protocol associatedType and <>

Defined associated type makes classes which conform protocol strong typed. This provide compile-time error handling.

In other hand, generic type makes classes which conform protocol more flexible.

For example:

protocol AssociatedRepository {
associatedtype T
func add(data : T) -> Bool
}

protocol GenericRepository {
func add(data : T) -> Bool
}


class A: GenericRepository {
func add(data : T) -> Bool {
return true
}
}

class B: AssociatedRepository {
typealias T = UIViewController
func add(data : T) -> Bool {
return true
}
}

class A could put any class into add(data:) function, so you need to makes your sure that function handle all cases.

A().add(data: UIView())
A().add(data: UIViewController())

both would be valid

But for class B you will get compile-time error when you will try to put anything except UIViewController

B().add(data: UIView()) // compile-time error here
B().add(data: UIViewController())


Related Topics



Leave a reply



Submit