Swift Protocol with Associated Type - Type May Not Reference Itself as a Requirement

error: type may not reference itself as a requirement associatedtype Suffix: SuffixableContainer where Suffix.Item == Item

protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer
// ...
}

uses a “recursive constraint” on the associated type: The associated
type Suffix is constrained to the enclosing protocol SuffixableContainer.

Recursive constraints on associated types were were implemented in Swift 4.1, i.e. that code requires Xcode 9.3 (currently in beta).

From the Swift CHANGELOG:

Swift 4.1

...

SE-0157 is implemented. Associated types can now declare "recursive" constraints, which require that the associated type conform to the enclosing protocol.

Swift Protocol referencing itself and associated type

You would look after something like this:

protocol Handler {
// ...
var next: some Handler<HandlerRequest == Self.HandlerRequest>? { get }
// ...
}

The problem here is that Swift doesn't (yet) have support for opaque return types that are protocols with associated types.

A solution to this limitation is to use a type eraser for the next property:

protocol Handler {
associatedtype HandlerRequest

// shift the generic from a protocol with associated type to a generic struct
var next: AnyHandler<HandlerRequest>? { get }

func handle(_ request: HandlerRequest) -> LocalizedError?
}

struct AnyHandler<HandlerRequest> {

private var _handle: (HandlerRequest) -> LocalizedError?
private var _next: () -> AnyHandler<HandlerRequest>?

init<H: Handler>(_ handler: H) where H.HandlerRequest == HandlerRequest {
_next = { handler.next }
_handle = handler.handle
}
}

extension AnyHandler: Handler {

var next: AnyHandler<HandlerRequest>? { return _next() }

func handle(_ request: HandlerRequest) -> LocalizedError? {
return _handle(request)
}
}

This way you can benefit both the protocol, and having the next property tied to the handler request type that you need.

As an added bonus for using protocols, you can still benefit the default implementation from the base class:

extension Handler {
func handle(_ request: HandlerRequest) -> LocalizedError? {
return next?.handle(request)
}
}

That's how cool protocols are in Swift, they allow you to avoid classes and use value types for as much as possible, by improving the concept of polymorphism.


Usage example:

struct LoginHandler: Handler {
var next: AnyHandler<AccountRequest>?

func handle(_ request: AccountRequest) -> LocalizedError? {
// do the login validation
}
}

struct SignupHandler: Handler {
var next: AnyHandler<AccountRequest>?

func handle(_ request: AccountRequest) -> LocalizedError? {
// do the signup validation
}
}

extension Handler {
// Helper function to easily create a type erased AnyHandler instance
func erase() -> AnyHandler<HandlerRequest> {
return AnyHandler(self)
}
}

// now let's put the handers to work:
let loginHandler = LoginHandler()
let signupHandler = SignupHandler(next: loginHandler.erase())
let someOtherAccountHandler = SomeOtherAccountHandler(next: signupHandler.erase())

Swift protocol with associatedtype error

You have a couple of options.

Firstly you can use a specific type of FooDelegate, like SomeFoo:

class Bar {
//In Bar instance runtime ,it will call delegate to do something...
var delegate: SomeFoo!
}

Or you can make Bar generic and define what type of Item the delegate needs:

class Bar<F> where F: FooDelegate, F.Item == Int {
//In Bar instance runtime ,it will call delegate to do something...
var delegate: F!
}

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<T : Address> : Int, FormRepresentable {

// ...

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

// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom<ShippingAddress>.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))

Swift - Protocol can only be used as a generic constraint because it has Self or associated type requirements

You have to turn these requirements around;

Instead of injecting a MicroServiceProvider into each request, you should write a generic MicroService 'Connector' Protocol that should define what it expects from each request, and what each request expects it to return.

You can then write a TestConnector which conforms to this protocol, so that you have complete control over how your requests are handled. The best part is, your requests won't even need to be modified.

Consider the following example:

protocol Request {
// What type data you expect to decode and return
associatedtype Response

// Turn all the data defined by your concrete type
// into a URLRequest that we can natively send out.
func makeURLRequest() -> URLRequest

// Once the URLRequest returns, decode its content
// if it succeeds, you have your actual response object
func decode(incomingData: Data?) -> Response?
}

protocol Connector {
// Take in any type conforming to Request,
// do whatever is needed to get back some potential data,
// and eventually call the handler with the expected response
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void)
}

These are essentially the bare minimum requirements to setup such a framework. In real life, you'll want more requirements from your Request protocol (such as ways to define the URL, request headers, request body, etc).

The best part is, you can write default implementations for your protocols. That removes a lot of boilerplate code! So for an actual Connector, you could do this:

extension Connector {
func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void) {
// Use a native URLSession
let session = URLSession()

// Get our URLRequest
let urlRequest = request.makeURLRequest()

// define how our URLRequest is handled
let task = session.dataTask(with: urlRequest) { data, response, error in
// Try to decode our expected response object from the request's data
let responseObject = request.decode(incomingData: data)

// send back our potential object to the caller's completion block
handler(responseObject)
}

task.resume()
}
}

Now, with that, all you need to do is implement your ProfilePictureRequest like this (with extra example class variables):

struct ProfilePictureRequest: Request {
private let userID: String
private let useAuthentication: Bool

/// MARK: Conform to Request
typealias Response = UIImage

func makeURLRequest() -> URLRequest {
// get the url from somewhere
let url = YourEndpointProvider.profilePictureURL(byUserID: userID)

// use that URL to instantiate a native URLRequest
var urlRequest = URLRequest(url: url)

// example use: Set the http method
urlRequest.httpMethod = "GET"

// example use: Modify headers
if useAuthentication {
urlRequest.setValue(someAuthenticationToken.rawValue, forHTTPHeaderField: "Authorization")
}

// Once the configuration is done, return the urlRequest
return urlRequest
}

func decode(incomingData: Data?) -> Response? {
// make sure we actually have some data
guard let data = incomingData else { return nil }

// use UIImage's native data initializer.
return UIImage(data: data)
}
}

If you then want to send a profile picture request out, all you then need to do is (you'll need a concrete type that conforms to Connector, but since the Connector protocol has default implementations, that concrete type is mostly empty in this example: struct GenericConnector: Connector {}):

// Create an instance of your request with the arguments you desire
let request = ProfilePictureRequest(userID: "JohnDoe", useAuthentication: false)

// perform your request with the desired Connector
GenericConnector().perform(request) { image in
guard let image = image else { return }

// You have your image, you can now use that instance whichever way you'd like
ProfilePictureViewController.current.update(with: image)
}

And finally, to set up your TestConnector, all you need to do is:

struct TestConnector: Connector {

// define a convenience action for your tests
enum Behavior {
// The network call always fails
case alwaysFail

// The network call always succeeds with the given response
case alwaysSucceed(Any)
}

// configure this before each request you want to test
static var behavior: Behavior

func perform<T: Request>(request: T, handler: @escaping (T.Response?) -> Void) {
// since this is a test, you don't need to actually perform any network calls.
// just check what should be done
switch Self.behavior {
case alwaysFail:
handler(nil)

case alwaysSucceed(let response):
handler(response as! T)
}
}
}

With this, you can easily define Requests, how they should configure their URL actions and how they decode their own Response type, and you can easily write mocks for you connectors.

Of course, keep in mind that the examples given in this answer are quite limited in how they can be used. I would highly suggest you to take a look at this library I wrote. It extends this example in a much more structured way.

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<DataSource: DataSourceCompatible>: UIView {
private var viewModel: DataSource.CellModel!

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

let vc = DataSourceViewController<AddressBookViewModel>()


Related Topics



Leave a reply



Submit