【问题标题】:Chain requests and return both results with RxSwift使用 RxSwift 链接请求并返回两个结果
【发布时间】:2025-11-29 16:05:01
【问题描述】:

我有一个ContentService 请求文章。该文章响应包含 authorId 属性。

我有一个ProfileService,它允许我通过userId 请求用户配置文件。

我正在尝试从ContentService 请求一篇文章,一旦使用authorId 属性完成到ProfileService 的请求,我想返回一个包含这两篇文章的ContentArticleViewModel和个人资料信息。

我的ArticleInteractor 看起来像这样 -

final class ArticleInteractor: ArticleInteractorInputProtocol {

    let fetchArticleTrigger = PublishSubject<String>()

    private lazy var disposeBag = DisposeBag()

    weak var output: ArticleInteractorOutputProtocol? {
        didSet {
            configureSubscriptions()
        }
    }

    private func configureSubscriptions() {
        guard let output = output else { return }

        fetchArticleTrigger
            .bind(to: dependencies.contentSvc.fetchContentByIdTrigger)
            .disposed(by: disposeBag)

        dependencies.contentSvc.fetchContentByIdResponse
            .bind(to: output.fetchArticleResponse)
            .disposed(by: disposeBag)
    }
}

很简单fetchArticleTrigger 发起请求,然后我在dependencies.contentSvc.fetchContentByIdResponsebind 并获取响应。

ContentService上的方法是-

    // MARK:- FetchContentById
    // @params: id - String
    // return: PublishSubject<ContentArticle>

        fetchContentByIdTrigger
            .flatMapLatest { [unowned self] in self.client.request(.getContentById(id: $0)) }
            .map { (resp: Result<ContentArticle>) in
                guard case .success(let props) = resp else { return ContentArticle() }
                return props
        }
        .bind(to: fetchContentByIdResponse)
        .disposed(by: disposeBag)

我的ProfileService 上有一个非常相似的设置-

    // MARK:- FetchUserProfileById
    // @params: id - String
    // return: PublishSubject<User>
        fetchUserProfileByIdTrigger
            .flatMapLatest { [unowned self] in self.client.request(.getProfileByUserId(id: $0)) }
            .map { (resp: Result<User>) in
                guard case .success(let props) = resp else { return User() }
                return props
        }
        .bind(to: fetchUserProfileByIdResponse)
        .disposed(by: disposeBag)

我想我会为这篇文章创建一个模型,比如 -

struct ContentArticleViewModel {
    var post: ContentArticle
    var user: User
}

我在我的ArticleInteractor-中成像了类似这样的伪代码-

    dependencies.contentSvc.fetchContentByIdResponse
        .flatMapLatest { article in
             /* fetch profile using `article.authorId */
        }.map { article, profile in
            return ContentArticleViewModel(post: article, user: profile)
        }
        .bind(to: output.fetchArticleResponse)
        .disposed(by: disposeBag)

但我完全不知道如何最好地处理这个问题。我看过很多关于链接请求的文章,但我很难成功地应用任何东西。

编辑

我目前有一些工作 -

    private func configureSubscriptions() {
        guard let output = output else { return }

        fetchArticleTrigger
            .bind(to: dependencies.contentSvc.fetchContentByIdTrigger)
            .disposed(by: disposeBag)

        dependencies.contentSvc.fetchContentByIdResponse
            .do(onNext: { [unowned self] article in self.dependencies.profileSvc.fetchUserProfileByIdTrigger.onNext(article.creator.userId)})
            .bind(to: fetchArticleResponse)
            .disposed(by: disposeBag)

        let resp = Observable.combineLatest(fetchArticleResponse, dependencies.profileSvc.fetchUserProfileByIdResponse)

        resp
            .map { [unowned self] in self.enrichArticleAuthorProps(article: $0, user: $1) }
            .bind(to: output.fetchArticleResponse)
            .disposed(by: disposeBag)
    }

    private func enrichArticleAuthorProps(article: ContentArticle, user: User) -> ContentArticle {
        var updatedArticle = article
        updatedArticle.creator = user
        return updatedArticle
    }

但我不确定这是否正确。

【问题讨论】:

    标签: swift rx-swift chaining


    【解决方案1】:

    我不知道你为什么要为这么小的工作编写这么多代码。下面是一个示例,它执行您所描述的操作(下载文章、下载作者个人资料并发出两者),代码少得多,甚至 it 的代码也比我通常使用的要多。

    protocol ContentService {
        func requestArticle(id: String) -> Observable<Article>
        func requestProfile(id: String) -> Observable<User>
    }
    
    class Example {
        let service: ContentService
        init(service: ContentService) {
            self.service = service
        }
    
        func bind(trigger: Observable<String>) -> Observable<(Article, User)> {
            let service = self.service
            return trigger
                .flatMapLatest { service.requestArticle(id: $0) }
                .flatMapLatest {
                    Observable.combineLatest(Observable.just($0), service.requestProfile(id: $0.authorId))
                }
        }
    }
    

    或者您可能希望在等待作者资料下载时显示文章。在这种情况下是这样的:

    func bind(trigger: Observable<String>) -> (article: Observable<Article>, author: Observable<User>) {
        let service = self.service
        let article = trigger
            .flatMapLatest { service.requestArticle(id: $0) }
            .share(replay: 1)
    
        let author = article
            .flatMapLatest {
                service.requestProfile(id: $0.authorId)
        }
    
        return (article, author)
    }
    

    【讨论】:

    • 啊完美!我真的把事情复杂化了——我现在明白了,谢谢丹尼尔,这帮助我指明了一个更清晰的方向