如何用Combine和Swift直接返回嵌套字典内数组的Publisher
解决方案:让Combine网络层直接返回嵌套数组的Publisher
要实现直接返回AnyPublisher<[Entity], Error>,无需在sink中二次处理,你可以通过在数据流中提前提取嵌套数组并处理可能的错误来实现,以下是具体步骤:
1. 修正NetworkController的代码错误
你当前的get方法中有重复的return URLSession.shared.dataTaskPublisher调用,先修正这个问题:
func get<T>(type: T.Type, url: URL, headers: Headers) -> AnyPublisher<T, Error> where T : Decodable { var urlRequest = URLRequest(url: url) headers.forEach { key, value in if let value = value as? String { urlRequest.setValue(value, forHTTPHeaderField: key) } } return URLSession.shared.dataTaskPublisher(for: urlRequest) .map(\.data) .decode(type: T.self, decoder: JSONDecoder()) .eraseToAnyPublisher() }
2. 自定义网络错误类型(可选但推荐)
为了更清晰地处理"缺失entities数组"这类边界情况,定义一个自定义错误:
enum NetworkError: Error { case missingEntities // 可根据需求扩展其他错误类型,比如invalidResponse等 }
3. 修改EntitiesLogicController的getEntities方法
让它直接返回[Entity]的Publisher,通过tryMap在数据流中提取并验证数组:
func getEntities(named: String, count: Int) -> AnyPublisher<[Entity], Error> { let endpoint = Endpoint.companies(named: named, count: count) return networkController.get(type: Entities.self, url: endpoint.url, headers: endpoint.headers) .tryMap { entitiesResponse in // 安全解包entities数组,若为nil则抛出自定义错误 guard let entities = entitiesResponse.entities else { throw NetworkError.missingEntities } return entities } .eraseToAnyPublisher() }
如果你能确定API返回的
entities永远不为nil,可以简化为return entitiesResponse.entities!,但推荐用tryMap+错误抛出的方式处理边界情况。
4. 简化ApiCaller的getEntities方法
现在可以直接接收[Entity]类型的数据流,无需在sink中二次处理:
func getEntities(named: String) { entitiesLogicController?.getEntities(named: named, count: 20) .sink(receiveCompletion: { [weak self] completion in switch completion { case .failure(let error): print("Error retrieving Entity, ERROR: \(error)") case .finished: print("Data successfully downloaded") } }, receiveValue: { [weak self] entities in self?.entities = entities }) .store(in: &subscriptions) }
进阶:封装通用的数组提取方法(可选)
如果你的API有很多类似的嵌套数组结构,可以给NetworkController扩展一个通用方法,方便复用:
extension NetworkController { func getNestedArray<Root, Element>( rootType: Root.Type, arrayKeyPath: KeyPath<Root, [Element]?>, url: URL, headers: Headers ) -> AnyPublisher<[Element], Error> where Root: Decodable, Element: Decodable { return get(type: rootType, url: url, headers: headers) .tryMap { root in guard let array = root[keyPath: arrayKeyPath] else { throw NetworkError.missingArray } return array } .eraseToAnyPublisher() } // 补充对应的错误类型 enum NetworkError: Error { case missingArray case missingEntities } }
使用时只需传入根类型和数组的KeyPath:
func getEntities(named: String, count: Int) -> AnyPublisher<[Entity], Error> { let endpoint = Endpoint.companies(named: named, count: count) return networkController.getNestedArray( rootType: Entities.self, arrayKeyPath: \.entities, url: endpoint.url, headers: endpoint.headers ) }
内容的提问来源于stack exchange,提问作者M4tta3us
相关产品推荐
相关产品推荐

