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.
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 protocols with generic arguments
The "generic parameters" that you are seeing are the protocols' primary associated types, proposed in SE-0346, implemented in Swift 5.7, I suppose the grammar section in the language reference just hasn't been updated yet.
The three protocol declarations are semantically different, in that they have different primary associated types. When using the protocol in certain positions, primary associated types are what you can directly specify in <...>
, rather than specify them somewhere else like in a where
clause. For example, when using the protocol as a generic constraint:
func foo<P: TestProtocol2<Int>>(p: P) { ... }
is syntactic sugar for:
func foo<P: TestProtocol2>(p: P) where P.T == Int { ... }
The former is just a little more concise :)
You cannot do something similar with TestProtocol1
, because it doesn't have primary associated types.
For TestProtocol3
, you must specify both primary associated types:
func foo<P: TestProtocol3<Int, Bool>>(p: P) { ... }
According to the SE proposal, this syntax was also planned to be usable in the protocol conformance clause of a concrete type, like in your code:
class TestClass3 : TestProtocol3<Int, Bool> { // does not compile
However, this feature did not get added for some reason. You can still use it in the inheritance clause of a protocol though:
protocol TestProtocol4: TestProtocol3<Int, Bool> { } // works
See the SE proposal for more details.
Related Topics
Swift 4, Simultaneous Access to Tuple Members as Inout
Uiscrollview Controller Not Scrolling Fully
How to Update User Interface on Core Data
Find Index of Annotation Mapkit
Metal: Limit Mtlrendercommandencoder Texture Loading to Only Part of Texture
Swift Coredata: Unable to Section Tableview Using Sectionnamekeypath with Custom Function
How to Rotate Text in Nsview? (Mac Os, Cocoa, Swift)
How to Customise UIsearchcontroller
Adding a UIvisualeffectview to Button
Binary Search: Error in Passing Array
How to Use a Protocol with a Typealias as a Func Parameter
Social Framework Is Deprecated in iOS11
Saving a Screen Recording with Rpscreenrecorder Start Capture
How to Stop Dispatchgroup or Operationqueue Waiting
iOS 14 Widget Detect System Theme Change