RxSwift, загрузки, зависящие от цепочки, возвращают один и тот же тип Observable

Я загружаю список книг из API и для каждой книги хочу скачать обложку.

Я застрял с тем, что я думаю, что это очень простая и распространенная задача в Rx. Я провел исследование, но не могу найти решение, потому что все связанные с этим вопросы касаются того, как вернуть еще один Observable; например, получить репозитории Github, а затем получить проблемы для каждого из них, но возвращая Observable<Issue>, а не измененный Observable<Repository>, это мой случай. В моем случае я хочу изменить предыдущий наблюдаемый результат (Observable<[Book]>) с результатом другого запроса, возвращающего другой наблюдаемый (Observable<Data>).

Пока у меня есть первая часть, качайте книги:

func search(query: String) -> Observable<[Book]> {
    return networkSession.rx
        .data(request: URLRequest(url: url))
        .map({ try JSONDecoder().decode([Book].self, from: $0) })
}

Book — это простая структура:

struct Book {
    let title: String
    let authors: [String]
    let coverImageURL: URL
    var coverImage: Data?
}

После этого я могу загрузить каждое изображение, но я не знаю, как назначить его каждому объекту, чтобы вернуть один и тот же тип Observable<[Book]>, не связываясь с вложенными наблюдаемыми и миллионом ошибок времени компиляции. Я почти уверен, что это обычный сценарий использования flatMap, но он все еще неясен для меня.

Большое спасибо!


person emenegro    schedule 27.04.2018    source источник


Ответы (1)


Возможно, следует разделить структуру Book на две структуры. Первый здесь называется BookInfo, а их массив загружается с вызовом функции search.

struct BookInfo {
    let title: String
    let authors: [String]
    let coverImageURL: URL
}

Составление экземпляра BookInfo вместе с данными coverImage может привести к структуре Book, подобной этой:

struct Book {
    let bookInfo: BookInfo
    let coverImage: Data
}

Цепочка RxSwift будет выглядеть так:

self.search(query: "<someQuery>") 
.flatMap { (bookInfos: [BookInfo]) -> Observable<BookInfo> in
    Observable.from(bookInfos)
}
.flatMap { (bookInfo: BookInfo)  -> Observable<Book> in
    //use the coverImageURL from the closure parameter and fetch the coverImage
    //return an Observable<Book>  
}
.observeOn(MainScheduler.instance)
.do(onNext: { (book: Book) in
    //e.g. add a book in main thread to the UI once the cover image is available
})
.subscribe()
.disposed(by: disposeBag)

Вместо использования вывода типов я добавил явные спецификации типов, чтобы было более очевидно, какие элементы передаются через цепочку.

ОБНОВЛЕНИЕ

Если вы хотите использовать только одну структуру Book, вы можете использовать это:

self.search(query: "<someQuery>") 
.flatMap { (books: [Book]) -> Observable<Book> in
    Observable.from(books)
}
.flatMap { (book: Book)  -> Observable<Book> in
    //use the book.coverImageURL from the closure parameter and fetch the coverImage
    //since the closure parameter 'book' is a let constant you have to construct a new Book instance 
    //return an Observable<Book>     
}
...

Обратите внимание, что параметры замыкания являются константами let. Это означает, что вы не можете изменить свойство coverImage, даже если оно определено как var в структуре. Вместо этого вы должны создать новый экземпляр структуры Book, заполнить его значениями из параметра Closure и добавить данные coverImage. Соответственно, если вы хотите пойти по этому пути, вы также можете при желании изменить coverImage на константу let.

Думаю, лично я бы предпочел первый подход с двумя структурами.

person Stephan Schlecht    schedule 28.04.2018
comment
Отличный подход, мне нравится :D Я попробую. Позвольте мне подождать, пока кто-нибудь ответит, не используя две структуры, прежде чем принять ваш ответ. - person emenegro; 30.04.2018
comment
Рад, что тебе понравилось :D. Я обновил ответ и добавил второй подход без использования двух структур. - person Stephan Schlecht; 30.04.2018
comment
Спасибо за обновление, это то, что я искал :) - person emenegro; 04.05.2018