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

时间:2021-05-29 10:01:54

标签: swift generics protocols where-clause associated-types

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

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

2 个答案:

答案 0 :(得分:1)

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

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

这就是 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)
        }
    }

这有点复杂,可能需要时间来理解。

答案 1 :(得分:0)

您可以创建类型擦除的提供程序:

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

用法:

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

拥有 resp 类型的 Any? 可能不切实际,但您没有指定您打算用它做什么。假设您想对您的回复做些事情,您可以创建一个协议:

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

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

并以这种方式定义 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"

相关问题