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

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

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

// 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/。您可以在 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)
        }
    }

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

,

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

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"

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res