Protocol Having Generic Function and Associatedtype

Protocol with associatedtype Protocol for Generic functions

This would perhaps be easier is you gave responsibility of the return types to the object classes themselves.

You would need two protocols but it would avoid mixing protocols and generics:

// The first protocol is for the base class of data objects
protocol DataProtocol
{}

// The protocol provides the "typed" equivalents of the model's
// data manipulation methods.
// By using an extension to DataProtocol, this only needs to
// be done once for all models and data objects.
extension DataProtocol
{
static func fetchObjects(from model:DataModelProtocol) -> [Self]?
{ return model.fetchObjects(object: Self.self) as! [Self]? }

static func fetch(from model:DataModelProtocol) -> Self?
{ return model.fetch(object: Self.self) as! Self? }

// ...
}

// The second protocol is for the data models
// It requires implementation of the data manipulation methods
// using the general "DataProtocol" rather than any specific class
// The actual instances it produces must be of the appropriate class
// however because they will be type casted by the DataProtocol's
// default methods
protocol DataModelProtocol
{
func fetchObjects(object:DataProtocol.Type) -> [DataProtocol]?
func fetch(object:DataProtocol.Type) -> DataProtocol?
// ... and so on
}

...
Here is a simple (naive) example of how the protocols can be used. (I intentionally chose not to use core data to illustrate the generality of the solution)
...

// The base class (or each one) can be assigned the DataProtocol
// (it doesn't add any requirement)

class LibraryObject : DataProtocol
{}

class Author: LibraryObject
{
var name = ""
}

class Book: LibraryObject
{
var title = ""
}

// This is a simple class that implements a DataModelProtocol
// in a naive (and non-core-data way)

struct LibraryModel:DataModelProtocol
{
var authors:[Author] = [ Author(), Author() ]

var books:[Book] = [ Book(), Book(), Book(), Book(), Book() ]

func fetchObjects(object: DataProtocol.Type) -> [DataProtocol]?
{
return object == Book.self ? books
: object == Author.self ? authors
: nil
}

func fetch(object:DataProtocol.Type) -> DataProtocol?
{ return nil }

}

...
Using the protocols will be a bit different from your approach because you would be starting from the object classes rather than passing them as parameter to the model
...

var library  = LibraryModel()
let allBooks = Book.fetchObjects(from:library) // this almost reads like english

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<T>(data : T) -> Bool
}

class A: GenericRepository {
func add<T>(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())

Swift: Question on Generic Functions and Associated Types

You are getting this error, because indeed self does not match all given constraints. By stating func test<M: Model>(model: M) -> M where M.ModelType == T you only say that this type M (which is only exists in the context of a call to your test function) is to be a Model, which's ModelType is the same as the ModelType of self. That does not mean, however, that Self and M are equivalent.

A little example using your types from above:

Assuming self is an instance of Implementation<Int>. In the context of a call to test(model:), M could be either of Implementation<Int> or Alternative<Int>. Returning self wouldn't be a problem in the first case, as both instances have the same type. However, in the second case you could assign an Implementation<Int> to an Alternative<Int>.

var a = Alternative<Int>()
let x = Implementation<Int>()
// This would assign something of type `Implementation<Int>` to a variable that
// may only contain `Alternative<Int>`.
var a = x.test(model: a)

I am sure there is a method to do what you want to achieve in Swift, but the solution totally depends on what that is.

How to pass protocol with associated type (generic protocol) as parameter in Swift?

If you use typealias in a protocol to make it generic-like, then you cannot use it as a variable type until the associated type is resolved. As you have probably experienced, using a protocol with associated type to define a variable (or function parameter) results in a compilation error:

Protocol 'MyProtocol' can only be used as a generic constraint because it has Self os associated type requirements

That means you cannot use it as a concrete type.

So the only 2 ways I am aware of to use a protocol with associated type as a concrete type are:

  • indirectly, by creating a class that implements it. Probably not what you have planned to do
  • making explicit the associated type like you did in your func

See also related answer https://stackoverflow.com/a/26271483/148357

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())

Use associated type protocol as a return type of a generic function

Protocols with associatedtype can’t be used in form of composition, which is a drawback and definitely irritating sometimes. But, you can create your own Type Erasure class to make this work.

You can study more about type erasure from this link: https://www.donnywals.com/understanding-type-erasure-in-swift/. You can find many more on Google.

This is how Apple has implemented it internally, by making few changes we can make it work our way.

Below is the code I came up with:

 //Let's say I have two possible type of responses
struct OtpResponse{}
struct SsoResponse{}

//A simple protocol to mandate the return of token from respective concrete type
protocol AuthenticationProvider{
associatedtype ResponseType
func getToken(completion: @escaping(ResponseType?, NSError?) -> Void)
}

//A type of auth provider
struct OtpBasedAuthProvider:AuthenticationProvider{

func getToken(completion: @escaping (OtpResponse?, NSError?) -> Void) {
let otpResponse = OtpResponse()
completion(otpResponse,nil)
}
}

//Another type of auth provider
struct SsoBasedAuthProvider:AuthenticationProvider{

func getToken(completion: @escaping (SsoResponse?, NSError?) -> Void) {
let ssoResponse = SsoResponse()
completion(ssoResponse,nil)
}
}

// there is some external logic to decide which type of auth provider to be used
func getProviderTypeFromSomeLogicOtherLogic() -> Int{
return 1//simply for dummy
}

Type Erasure:

class _AnyCacheBox<Storage>:AuthenticationProvider{
func getToken(completion: @escaping (Storage?, NSError?) -> Void) {
fatalError("Never to be called")
}

}

final class _CacheBox<C:AuthenticationProvider>: _AnyCacheBox<C.ResponseType>{
private var _base:C

init(base:C) {
self._base = base
}

override func getToken(completion: @escaping (C.ResponseType?, NSError?) -> Void) {
_base.getToken(completion: completion)
}
}

struct AnyCache<Storage>:AuthenticationProvider{
private let _box: _AnyCacheBox<Storage>

init<C:AuthenticationProvider>(cache:C) where C.ResponseType == Storage {
_box = _CacheBox(base: cache)
}

func getToken(completion: @escaping (Storage?, NSError?) -> Void) {
_box.getToken(completion: completion)
}
}


//Factory to return a concrete implementaton of auth provider
class AuthProviderFactory{
func getOTPAuthProvider() -> AnyCache<OtpResponse>{

let obj : AnyCache = AnyCache(cache: OtpBasedAuthProvider())
return obj

}

func getSSoAuthProvider() -> AnyCache<SsoResponse>{
let obj : AnyCache = AnyCache(cache: SsoBasedAuthProvider())
return obj
}
}

Below is how client can invoke methods in Factory-:

func executeNetworkCall() -> Void{
let factory = AuthProviderFactory()
let authProvider = factory.getOTPAuthProvider()
authProvider.getToken{(resp,error) in
//some code
print(resp)
}
}

It’s a bit involving and could take time to understand.



Related Topics



Leave a reply



Submit