简体   繁体   English

使用关联类型协议作为泛型 function 的返回类型

[英]Use associated type protocol as a return type of a generic function

Protocols with associated types is confusing:具有关联类型的协议令人困惑:

// Lets 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 {
    typealias ResponseType = OtpResponse
    func getToken(completion: @escaping (OtpResponse?, NSError?) -> Void) {
        let otpResponse = OtpResponse()
        completion(otpResponse, nil)
    }
}

// Another type of auth provider
struct SsoBasedAuthProvider: AuthenticationProvider {
    typealias ResponseType = SsoResponse
    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
}

// Factory to return a concrete implementaton of auth provider
class AuthProviderFactory {
    func getAuthProvider<T: AuthenticationProvider>(type:Int) -> T {
        if type == 1 {
            return SsoBasedAuthProvider() as! T
        }
        else {
            return OtpBasedAuthProvider() as! T
        }
    }
}

Now to use the code above, I want to do something like this:现在要使用上面的代码,我想做这样的事情:

func executeNetworkCall() -> Void {
    let factory = AuthProviderFactory() // 1
    let authProvider = factory.getAuthProvider(type:  getProviderTypeFromSomeLogicOtherLogic()) // 2
    authProvider.getToken{ (resp, error) in // 3
        // some code
    }
}

In the above, line number 2 where I am trying to get provider type from factory is giving me error as:在上面,我试图从工厂获取提供程序类型的第 2 行给了我错误:

Generic parameter 'T' could not be inferred .无法推断通用参数“T”

I know I can get rid of compilation error by doing something like this:我知道我可以通过执行以下操作来摆脱编译错误:

let authProvider:SsoBasedAuthProvider = factory.getAuthProvider(type: getProviderTypeFromSomeLogicOtherLogic())

But thats not the point, I dont know which provider will be returned and I want to call.getToken from that provider.但这不是重点,我不知道将返回哪个提供程序,我想从该提供程序调用.getToken。

Protocols with associatedtype can't be used in form of composition, which is a drawback and definitely irritating sometimes.具有associatedtype类型的协议不能以组合的形式使用,这是一个缺点,有时肯定很烦人。 But, you can create your own Type Erasure class to make this work.但是,您可以创建自己的Type Erasure class 来完成这项工作。

You can study more about type erasure from this link: https://www.donnywals.com/understanding-type-erasure-in-swift/ .您可以通过以下链接了解有关类型擦除的更多信息: 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.这就是 Apple 在内部实现它的方式,只需进行一些更改,我们就可以让它按照我们的方式工作。

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 -:下面是客户端如何调用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.这有点涉及,可能需要时间来理解。

You can create a type-erased provider:您可以创建一个类型擦除的提供程序:

struct AnyAuthProvider: AuthenticationProvider {
    var getToken: (@escaping (Any?, NSError?) -> Void) -> Void
    init<T: AuthenticationProvider>(provider: T) {
        self.getToken = provider.getToken
    }
    
    func getToken(completion: @escaping (Any?, NSError?) -> Void) {
        getToken(completion)
    }
}

class AuthProviderFactory{
    func getAuthProvider(type:Int) -> AnyAuthProvider {
        if(type == 1 ){
            return AnyAuthProvider(provider: SsoBasedAuthProvider())
        }
        else{
            return AnyAuthProvider(provider: OtpBasedAuthProvider())
        }
    }
}

usage:用法:

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

func executeNetworkCall() -> Void{
    let factory = AuthProviderFactory()
    let authProvider = factory.getAuthProvider(type:getProviderTypeFromSomeLogicOtherLogic())
    authProvider.getToken{(resp,error) in // (Any?, NSError)
        //some code
        print(String(describing: resp))
    }
}

executeNetworkCall() // Optional(__lldb_expr_3.SsoResponse())
type = 2
executeNetworkCall() // Optional(__lldb_expr_3.OtpResponse())

Having resp of type Any?resp类型的Any? may not be practical but you did not specify what you intend to do with it.可能不实用,但您没有指定您打算用它做什么。 Let's say that you want to do stuff with your responses, you can create a protocol:假设您想对您的响应做一些事情,您可以创建一个协议:

protocol Response {
    func doStuff()
}
struct OtpResponse: Response {
    func doStuff() {
        print("OtpResponse")
    }
}

struct SsoResponse: Response {
    func doStuff() {
        print("SsoResponse")
    }
}

and define AnyAuthProvider this way:并以这种方式定义AnyAuthProvider

struct AnyAuthProvider: AuthenticationProvider {
    var getToken: (@escaping (Response?, NSError?) -> Void) -> Void
    init<T: AuthenticationProvider>(provider: T) where T.ResponseType: Response {
        self.getToken = provider.getToken
    }
    
    func getToken(completion: @escaping (Response?, NSError?) -> Void) {
        getToken(completion)
    }
}

func executeNetworkCall() -> Void{
    let factory = AuthProviderFactory()
    let authProvider = factory.getAuthProvider(type:getProviderTypeFromSomeLogicOtherLogic())
    authProvider.getToken{(resp,error) in
        //some code
        resp?.doStuff()
    }
}

executeNetworkCall() // prints "SsoResponse"
type = 2
executeNetworkCall() // prints "OtpResponse"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM