swift 具有相关类型的协议编译错误:Protocol只能用作泛型约束,因为它具有Self或关联的类型要求

ykejflvf  于 2023-03-16  发布在  Swift
关注(0)|答案(2)|浏览(122)

我的目标是创建一个模块化的网络服务层,可以很容易地进行单元测试。
为了实现这一目标,我简单地从以下协议开始:

protocol NetworkService {
    associatedtype Response
    func fetchData() async throws -> Response
}

然后,我继续创建一个服务来获取新闻提要,如下所示:

struct FetchNewsService: NetworkService {
    typealias Response = [NewsAssetModel]
    
    func fetchData() async throws -> Response {
        guard let fetchNewsURL = URL(string: SystemConstants.NetworkConstants.fetchNewsEndpoint) else {
            throw NetworkError.invalidURL
        }
        
        let urlRequest = URLRequest(url: fetchNewsURL)
        let session = URLSession(configuration: .default)
        
        guard let (data, response) = try? await session.data(for: urlRequest) else {
            // Would need to handle no internet error as well
            throw NetworkError.server
        }
        
        if let httpResponse = response as? HTTPURLResponse,
           httpResponse.statusCode > 400 {
            throw NetworkError.server
        }
        
        guard let newsResponse = try? JSONDecoder().decode(NewsResponseModel.self,
                                                           from: data) else {
            throw NetworkError.decode
        }
        
        return newsResponse.assets
    }
}

在我的视图模型中,我希望请求获取新闻提要项,因此我希望将NetworkService类型注入到视图模型中。

class NewsAssetManager {
    private(set) var newsAssets: [NewsAssetModel] = []
    private var newsService: NetworkService // 1 - error on this line
    
    // 2 - and this line below
    init(withNewsService newsService: NetworkService = FetchNewsService()) {
        
    }
}

在属性声明中得到的第一个错误是Protocol 'NetworkService' can only be used as a generic constraint because it has Self or associated type requirements
我在初始化器中得到的第二个错误也是相同的Protocol 'NetworkService' can only be used as a generic constraint because it has Self or associated type requirements
我做错了什么?我如何修复/改进?

更新泛型

一个三个三个一个
最后一行Cannot convert return expression of type '[NewsAssetModel]' to return type 'T'出现错误

根据评论和答复更新

将Any & Some的组合添加到属性或初始化器中仍然无法消除所面临的编译错误。

我还通过终端和此代码确认,我至少在运行Swift 5.6:

#if swift(>=5.6)
        print("swift 5.6")
#endif
vltsax25

vltsax251#

这就是我在不使用关联类型时定义协议的方式。将函数改为泛型,类型应该符合Decodable

protocol NetworkService {
    func fetchData<Response: Decodable>(url: URL) async throws -> Response
}

然后将符合类型的实现更改为

struct FetchNewsService: NetworkService {
    func fetchData<Response: Decodable>(url: URL) async throws -> Response {
        let urlRequest = URLRequest(url: url)
        let session = URLSession(configuration: .default)

        guard let (data, response) = try? await session.data(for: urlRequest) else {
            throw NetworkError.server
        }

        if let httpResponse = response as? HTTPURLResponse,
           httpResponse.statusCode > 400 {
            throw NetworkError.server
        }

        do {
            return try JSONDecoder().decode(Response.self, from: data)
        } catch {
            throw NetworkError.decode(error)
        }
    }
}

现在,该实现并没有被硬编码为一个解决方案,实际上它是如此通用,以至于我们可以将其移动到协议的扩展中

extension NetworkService {
    func fetchData<Response: Decodable>(url: URL) async throws -> Response {
        // same code as above
    }
}

这意味着在本例中不再需要FetchNewsService,可以将其删除。
然后可以将NewsAssetManager更改为下面的代码,其中特定于此特定API和响应类型的所有代码现在都驻留在下面。

class NewsAssetManager {
    private(set) var newsAssets: [NewsAssetModel] = []
    private var newsService: NetworkService

    init(withNewsService newsService: NetworkService) {
        self.newsService = newsService
    }

    func fetchNews() async throws {
        guard let fetchNewsURL = URL(string: SystemConstants.NetworkConstants.fetchNewsEndpoint) else {
            throw NetworkError.invalidURL
        }
        let model: NewsResponseModel = try await newsService.fetchData(url: fetchNewsURL)
        newsAssets = model.assets
    }
}
5ssjco0h

5ssjco0h2#

当我把你的代码放进Xcode时,编译器告诉我如何修复它:
变更

private var newsService: NetworkService

private var newsService: any NetworkService

并改变

init(withNewsService newsService: NetworkService = FetchNewsService())

init(withNewsService newsService: any NetworkService = FetchNewsService())

唯一的区别是关键字any

相关问题