因此,我有这样的API调用序列,其中我获取员工详细信息,然后获取与员工关联的公司和项目详细信息。在两个提取完成后,我将两者结合起来并发布一个 fetchCompleted 事件。我已经隔离了下面的相关代码。
func getUserDetails() -> AnyPublisher<UserDetails, Error>
func getCompanyDetails(user: UserDetails) -> AnyPublisher<CompanyDetails, Error>
func getProjectDetails(user: UserDetails) -> AnyPublisher<ProjectDetails, Error>
如果我这样做,
func getCompleteUserDetails() -> AnyPublisher<UserFetchState, Never> {
let cvs = CurrentValueSubject<UserFetchState, Error>(.initial)
let companyPublisher = getUserDetails()
.flatMap { getCompanyDetails($0) }
let projectPublisher = getUserDetails()
.flatMap { getProjectDetails($0) }
companyPublisher.combineLatest(projectPublisher)
.sink { cvs.send(.fetchComplete) }
return cvs.eraseToAnyPublisher()
}
getUserDetails() 将被调用两次。我需要的是获取用户详细信息一次,然后将流一分为二,将其映射以获取公司详细信息和项目详细信息,然后重新组合两者。 有没有一种优雅(更扁平)的方式来执行以下操作。
func getCompleteUserDetails() -> AnyPublisher<UserFetchState, Never> {
let cvs = CurrentValueSubject<UserFetchState, Error>(.initial)
getUserDetails()
.sink {
let companyPublisher = getCompanyDetails($0)
let projectPublisher = getProjectDetails($0)
companyPublisher.combineLatest(projectPublisher)
.sink { cvs.send(.fetchComplete) }
}
return cvs.eraseToAnyPublisher()
}
Combine 的整个思想是构建一个数据流向动的管道。实际上,向动的可以是值或完成,其中完成可能是失败(错误)。所以:
你不需要发出信号,表明管道已经产生了它的值;该值到达管道的末端就是那个信号。
同样,您不需要发出管道工作已完成的信号;已生成将要生成的所有值的发布者会自动生成完成信号,因此该完成在管道末尾的到达就是该信号。
毕竟,当你收到一封信时,邮局不会打电话给你说:"你有邮件。 相反,邮递员把信递给你。你不需要被告知你已经收到了一封信;你只是收到它。
好吧,让我们演示一下。了解您自己的管道的关键只是跟踪在任何给定时刻什么样的价值正在向下传递。因此,让我们构建一个模型管道,以执行您需要完成的操作。我将假设三种类型的值:
struct User {
}
struct Project {
}
struct Company {
}
我会想象可以上网获取所有这些信息:用户独立,以及基于用户中包含的信息的项目和公司。我将通过提供为每种类型的信息返回发布者的实用程序函数来模拟这一点;在现实生活中,这些可能是递延的期货,但我只是为了简单起见而使用:
func makeUserFetcherPublisher() -> AnyPublisher<User,Error> {
Just(User()).setFailureType(to: Error.self).eraseToAnyPublisher()
}
func makeProjectFetcherPublisher(user:User) -> AnyPublisher<Project,Error> {
Just(Project()).setFailureType(to: Error.self).eraseToAnyPublisher()
}
func makeCompanyFetcherPublisher(user:User) -> AnyPublisher<Company,Error> {
Just(Company()).setFailureType(to: Error.self).eraseToAnyPublisher()
}
现在,让我们构建我们的管道。我认为我们的目标是生成我们收集的所有信息作为管道中的最终价值:用户、项目和公司。因此,我们的最终输出将是这三件事的元组。(当你做组合的事情时,元组很重要。将元组沿管道向下传递是非常常见的。
好的,让我们开始吧。一开始什么都没有,所以我们需要一个初始发布者来启动这个过程。这将是我们的用户提取器:
let myWonderfulPipeline = self.makeUserFetcherPublisher()
该管道末端出来的是用户。我们现在希望将该用户馈送到接下来的两个发布者中,获取相应的项目和公司。将发布者插入管道中间的方法是使用flatMap
。请记住,我们的目标是生成所有信息的元组。所以:
let myWonderfulPipeline = self.makeUserFetcherPublisher()
// at this point, the value is a User
.flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
// ?
}
// at this point, the value is a tuple: (User,Project,Company)
那么flatMap
问号在哪里?好吧,我们必须产生一个发布者来生产我们承诺的元组。最优秀的制图出版商是Zip。元组中有三个值,所以这是一个 Zip3:
let myWonderfulPipeline = self.makeUserFetcherPublisher()
.flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
// ?
let result = Publishers.Zip3(/* ? */)
return result.eraseToAnyPublisher()
}
那么我们在压缩什么?我们必须压缩出版商。嗯,我们知道其中两个出版商——他们是我们已经定义的出版商!
let myWonderfulPipeline = self.makeUserFetcherPublisher()
.flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
let pub1 = self.makeProjectFetcherPublisher(user: user)
let pub2 = self.makeCompanyFetcherPublisher(user: user)
// ?
let result = Publishers.Zip3(/* ? */, pub1, pub2)
return result.eraseToAnyPublisher()
}
我们快完成了!缺少的插槽中有什么?请记住,它必须是发布者。我们的目标是什么?我们希望传递从上游到达的同一用户。出版商是做什么的?只是!所以:
let myWonderfulPipeline = self.makeUserFetcherPublisher()
.flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
let pub1 = self.makeProjectFetcherPublisher(user: user)
let pub2 = self.makeCompanyFetcherPublisher(user: user)
let just = Just(user).setFailureType(to:Error.self)
let result = Publishers.Zip3(just, pub1, pub2)
return result.eraseToAnyPublisher()
}
我们完成了。没有糊涂,没有大惊小怪。这是一个生成(User,Project,Company)
元组的管道。订阅此管道的人不需要一些额外的信号;元组的到来就是信号。现在,订阅者可以对该信息执行某些操作。让我们创建订阅者:
myWonderfulPipeline.sink {
completion in
if case .failure(let error) = completion {
print("error:", error)
}
} receiveValue: {
user, project, company in
print(user, project, company)
}.store(in: &self.storage)
我们没有做任何非常有趣的事情——我们只是打印了元组内容。但是你看,在现实生活中,订阅者现在会对这些数据做一些有用的事情。
您可以使用zip
运算符获取一个Publisher
,只要其两个上游都发出一个值,因此getCompanyDetails
和getProjectDetails
一起zip
。
您也不需要Subject
来表示获取完成,您只需在flatMap
上调用map
即可。
func getCompleteUserDetails() -> AnyPublisher<UserFetchState, Error> {
getUserDetails()
.flatMap { getCompanyDetails(user: $0).zip(getProjectDetails(user: $0)) }
.map { _ in UserFetchState.fetchComplete }
.eraseToAnyPublisher()
}
但是,您不需要UserFetchState
来指示管道的状态(尤其是不应该丢弃提取的CompanyDetails
和管道中间的ProjectDetails
对象)。您应该简单地返回获取的CompanyDetails
并ProjectDetails
作为您的flatMap
的结果。
func getCompleteUserDetails() -> AnyPublisher<(CompanyDetails, ProjectDetails), Error> {
getUserDetails()
.flatMap { getCompanyDetails(user: $0).zip(getProjectDetails(user: $0)) }
.eraseToAnyPublisher()
}