【问题标题】:Use generic protocols as variable type使用泛型协议作为变量类型
【发布时间】:2023-04-10 14:29:01
【问题描述】:

我想为这样的数据获取服务创建一个通用协议:

protocol FetchDataDelegate: AnyObject {

    associatedtype ResultData

    func didStartFetchingData()
    func didFinishFetchingData(with data: ResultData)
    func didFinishFetchingData(with error: Error)
}

class Controller: UIViewController, FetchDataDelegate {

    func didStartFetchingData() {
        //...
    }

    func didFinishFetchingData(with data: ResultData) {
        //...
    }

    func didFinishFetchingData(with error: Error) {
        //...
    }
}

class NetworkManager {

    weak var delegate: FetchDataDelegate?
    //...
}

Controller 类将引用 NetworkManager 实例,通过它,我想启动网络操作。当操作结束时,我想调用适当的委托函数将结果传回控制器。但是通过这个设置,我得到了以下错误: 协议 'FetchDataDelegate' 只能用作通用约束,因为它具有 Self 或关联的类型要求 问题是我应该怎么做才能将此泛型协议用作变量类型?或者如果不可能,那么正确的方法是什么?谢谢!

【问题讨论】:

标签: swift


【解决方案1】:

虽然这与question David Smith linked 密切相关(您也应该阅读它),但值得单独回答,因为它是一个不同的具体用例,因此我们可以讨论它。

首先,假设您可以存储这个变量。你会用它做什么? NetworkManager 中的什么函数可以调用delegate.didFinishFetchingData?当您不知道ResultData 是什么时,您将如何生成它?

关键是这不是 PAT(具有关联类型的协议)的用途。这不是他们的目标。他们的目标是帮助您向其他类型添加扩展,或者限制可以将哪些类型传递给通用算法。出于这些目的,它们非常强大。但是你想要的是泛型,而不是协议。

您应该使用通用函数来处理特定调用的结果,而不是创建委托,而不是试图将每个视图控制器确定为特定的结果类型(无论如何这不是很灵活)。例如,以最简单、最不灵活但仍为您提供进度报告的方式:

struct APIClient {
    func fetch<Model: Decodable>(_: Model.Type,
                                 with urlRequest: URLRequest,
                                 completion: @escaping (Result<Model, Error>) -> Void)
    -> Progress {

        let session = URLSession.shared

        let task = session.dataTask(with: urlRequest) { (data, _, error) in
            if let error = error {
                completion(.failure(error))
            }
            else if let data = data {
                let decoder = JSONDecoder()
                completion(Result {
                    try decoder.decode(Model.self, from: data)
                })
            }
        }
        task.resume()
        return task.progress
    }
}

let progress = APIClient().fetch(User.self, with: urlRequest) { user in ... }

这种结构是解决这一类问题的基本方法。 It can be made much, much more flexible depending on your specific needs.

(如果您的didStartFetchingData 方法非常重要,以Progress 无法解决的方式,请发表评论,我将展示如何实现这种事情。这并不难,但这个答案很漂亮已经很久了。)

【讨论】:

  • 你说得对,我在想也许每个控制器都应该有一个不同的网络管理器,所以在这些类中我会知道哪个具体类型属于哪个类。谢谢你的回答,它帮助我更多地理解了这个问题。在您回答之前,我得到了这个“解决方案”:创建一个包含 start 和 error 方法的基本协议,并且每个控制器都有一个自己的协议,该协议使用缺少的功能扩展此基本协议,当然还有具体的参数类型。现在我发现您的方法更加灵活。
【解决方案2】:

this 可能重复

总而言之,您不能将泛型协议用作变量类型,它必须用作泛型约束,因为您在编译时不知道 ResultData 的类型

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    相关资源
    最近更新 更多