簡體   English   中英

使用關聯類型協議作為泛型 function 的返回類型

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

具有關聯類型的協議令人困惑:

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

具有associatedtype類型的協議不能以組合的形式使用,這是一個缺點,有時肯定很煩人。 但是,您可以創建自己的Type Erasure class 來完成這項工作。

您可以通過以下鏈接了解有關類型擦除的更多信息: 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)
        }
    }

這有點涉及,可能需要時間來理解。

您可以創建一個類型擦除的提供程序:

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"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM