合并从模型到 viewModel 的转换会给出错误“当 运行 .eraseToAnyPublisher() 时表达式类型不明确且没有更多上下文

Combine conversion from model to viewModel gives error "Type of expression is ambiguous without more context when running .eraseToAnyPublisher()

我想弄清楚为什么最后一个 .eraseToAnyPublisher() 会出现上述错误,对我来说似乎所有类型都已明确指定,不是吗?

static func searchUsers(query: String) -> AnyPublisher<[UserViewModel], Never> {
            // prepare URL
            let urlString = "\(baseURL)/search/users?q=\(query)"
            guard let url = URL(string: urlString) else  {
                return Just([]).eraseToAnyPublisher()
            }
            // get handle of native data task publisher
            let publisher = URLSession.shared.dataTaskPublisher(for: url)
                .handleEvents(
                    receiveSubscription: { _ in
                        activityIndicatorPublisher.send(true)
                    }, receiveCompletion: { _ in
                        activityIndicatorPublisher.send(false)
                    }, receiveCancel: {
                        activityIndicatorPublisher.send(false)
                    })
                .tryMap { data, response -> Data in
                    guard let httpResponse = response as? HTTPURLResponse,
                          httpResponse.statusCode == 200 else {
                              throw NetworkError.httpError
                          }
                    print(String(data: data, encoding: .utf8) ?? "")
                    return data
                }
                .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
                .map { [=11=].items }
                .flatMap({ users in
                    var userViewModels = [UserViewModel]()
                    users.forEach { user in
                        userViewModels.append(contentsOf: UserViewModel(with: user))
                    }
                    return userViewModels
                })
                .catch { err -> Just<[UserViewModel]> in
                    print(err)
                    return Just([])
                }
                .eraseToAnyPublisher() // <-- HERE IS THE ERROR
            return publisher
        }

不幸的是,对于那些复杂的 Combine 管道,有时编译器错误会显示在错误的行上。在你的情况下有两个问题,但不是编译器指向的地方。

一个是使用 flatMap 而不是 map

管道的这一部分:

let publisher = URLSession.shared.dataTaskPublisher(for: url)
    .handleEvents(
        receiveSubscription: { _ in
            activityIndicatorPublisher.send(true)
        }, receiveCompletion: { _ in
            activityIndicatorPublisher.send(false)
        }, receiveCancel: {
            activityIndicatorPublisher.send(false)
        })
        .tryMap { data, response -> Data in
             guard let httpResponse = response as? HTTPURLResponse,
                 httpResponse.statusCode == 200 else {
                 throw NetworkError.httpError
             }
             print(String(data: data, encoding: .utf8) ?? "")
             return data
        }
        .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
        .map { [=10=].items }

returns Publisher<[User], Error>.

接下来您要将其转换为 Publisher<[UserViewModel], Error>,为此您需要一个函数 map:

func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Upstream, T>

将一种类型的 Output 转换为另一种类型的 Output 而不是 flatMap:

func flatMap<T, P>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure

Output 转换为新的 Publisher

第二个问题是 append(contentsOf;),它需要 Sequence 个元素,如果是单个元素,您应该使用 append(),但更简单的方法是映射 [User][UserViewModel]:

{ users in
    users.map { user in
        UserViewModel(with: user)
    }
}

所以整个功能应该适用于这些更改:

static func searchUsers(query: String) -> AnyPublisher<[UserViewModel], Never> {
    // prepare URL
    let urlString = "\(baseURL)/search/users?q=\(query)"
    guard let url = URL(string: urlString) else  {
        return Just([]).eraseToAnyPublisher()
    }
    // get handle of native data task publisher
    let publisher = URLSession.shared.dataTaskPublisher(for: url)
        .handleEvents(
            receiveSubscription: { _ in
                activityIndicatorPublisher.send(true)
            }, receiveCompletion: { _ in
                activityIndicatorPublisher.send(false)
            }, receiveCancel: {
                activityIndicatorPublisher.send(false)
            })
        .tryMap { data, response -> Data in
            guard let httpResponse = response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                      throw NetworkError.httpError
                  }
            print(String(data: data, encoding: .utf8) ?? "")
            return data
        }
        .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
        .map { [=14=].items }
        .map { users in
            users.map { user in
                UserViewModel(with: user)
            }
        }
        .catch { err -> Just<[UserViewModel]> in
            print(err)
            return Just([])
        }
        .eraseToAnyPublisher()
    return publisher
}