Цепочка запросов и возвращает оба результата с помощью RxSwift - PullRequest
2 голосов
/ 04 ноября 2019

У меня есть ContentService, который делает запрос на статью. Этот ответ на статью содержит свойство authorId.

У меня есть ProfileService, который позволяет мне запросить профиль пользователя по userId.

Я пытаюсь запросить статью из цепочки ContentService по запросу один разкоторый завершается до ProfileService с использованием свойства authorId, тогда я хотел бы вернуть 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 запускает запрос, затем я bind на dependencies.contentSvc.fetchContentByIdResponse и принимаю ответ.

Метод на моем 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
}

IЯ представлял себе что-то вроде этого псевдокода в моем 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
    }

Однако я не уверен, что это правильно.

1 Ответ

0 голосов
/ 05 ноября 2019

Я не уверен, почему у вас так много кода для такой маленькой работы. Ниже приведен пример, который выполняет то, что вы описываете (загружает статью, загружает профиль автора и выдает оба) с гораздо меньшим количеством кода и даже это - это больше кода, чем я обычно использовал бы.

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)
}
...