swift - 使用关联类型协议作为泛型函数的返回类型
问题描述
具有关联类型的协议令人困惑:
// 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/ 。你可以在谷歌上找到更多。
这就是 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)
}
}
这有点涉及,可能需要时间来理解。
推荐阅读
- vb6 - 在数据网格视图中第二次删除一行不起作用
- jython - 如何根据值的 MD5 哈希在 Openrefine 中创建 UUID
- javascript - JavaScript 为 stringToColour 函数添加透明度选项
- c - 使用 pthread 和信号量的 C 语言中的读写器问题
- ios - Swift 5 - 导航页面
- php - 了解php错误处理和代码执行
- c++ - 如何在 Visual Studio 2017 中为 C++ 控制台应用程序添加头文件
- aws-lambda - aws lambda 上没有名为“psycopg2”的模块。解决这个问题的最佳方法是什么?
- authentication - Kafka 授权和认证端口
- java - 通过 XSLT 和 Java 产生不同的当前时间