首页 > 解决方案 > 使用关联类型协议作为泛型函数的返回类型

问题描述

具有关联类型的协议令人困惑:

// 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
        }
    }
}

现在要使用上面的代码,我想做这样的事情:

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

在上面,我试图从工厂获取提供程序类型的第 2 行给了我错误:

无法推断通用参数“T”

我知道我可以通过执行以下操作来摆脱编译错误:

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

但这不是重点,我不知道将返回哪个提供程序,我想从该提供程序调用 .getToken 。

标签: swiftgenericsprotocolswhere-clauseassociated-types

解决方案


协议associatedtype不能以组合的形式使用,这是一个缺点,有时肯定很烦人。但是,您可以创建自己的Type Erasure类来完成这项工作。

您可以通过此链接了解有关类型擦除的更多信息:https ://www.donnywals.com/understanding-type-erasure-in-swift/ 。你可以在谷歌上找到更多。

这就是 Apple 在内部实现它的方式,只需进行一些更改,我们就可以让它按照我们的方式工作。

下面是我想出的代码:

 //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
}

类型擦除

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
        }
    }

下面是客户端如何调用Factory中的方法:

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

这有点涉及,可能需要时间来理解。


推荐阅读