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



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

// 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类来实现这一点。

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

这就是苹果公司内部实施的方式,只需做出一些改变,我们就能让它按自己的方式运行。

下面是我想出的代码:

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

拥有Any?类型的resp可能不实用,但您没有指定要用它做什么。假设您想用您的响应做一些事情,您可以创建一个协议:

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"

最新更新