2016-12-01 2 views
5

Firs of whole Я новичок в rxswift, поэтому, я думаю, ответ очевиден, но на данный момент я не могу найти решение самостоятельно.Правильное использование RxSwift для запросов на цепочку, flatMap или что-то еще?

У меня есть две функции:

func downloadAllTasks() -> Observable<[Task]> 
func getTaskDetails(taskId: Int64) -> Observable<TaskDetails> 

Сначала один загружает список Задачу объектов с помощью запроса сети, второй один загрузки сведений о задаче для sepcific задачи (используя его идентификатор)

Что я хочу от достичь - загрузить все задачи, а затем для каждой задачи я хочу загрузить ее данные и подписаться на событие, запущенное, когда все детали задач готовы.

Итак, я должен как-то подписаться на Observable < [TaskDetails]>, но я не знаю, как это сделать.

 downloadAllTasks() 
     .flatMap{ 
      ... // flatMap? something else? 
     } 
     .subscribe(
      onNext: { details in 
       print("tasks details: \(details.map{$0.name})") 
     }) 
     .addDisposableTo(disposeBag) 

// EDIT

Благодаря Сильван Mosberger ответ я гораздо ближе к решению. Осталась одна проблема. Теперь у меня есть что-то вроде этого:

downloadAllTasks() 
     .flatMap{ Observable.from($0) } 
     .map{ $0.id } 
     .flatMap{ [unowned self] id in 
      self.getTaskDetails(taskId: id).catchError{ error in 
       print("$$$ Error downloading task \(id)") 
       return .empty() 
      } 
     } 
     .do(onNext: { _ in 
      print(" $$$ single task details downloaded") 
     }) 
     .toArray() 
     .debug("$$$ task details array debug", trimOutput: false) 
     .subscribe({ _ in 
      print("$$$ all tasks downloaded") 
     }) 
     .addDisposableTo(disposeBag) 

Выход на

$$$ task details array debug -> subscribed 
$$$ single task details downloaded 
$$$ single task details downloaded 
$$$ single task details downloaded 

Есть 3 задачи доступны так, как вы можете себе все они загружены правильно, однако по каким-то причинам результат ToArray() - (Observable<[TaskDetails]>) не создает «onNext» после того, как все детали задачи готовы.

// Edit еще раз

Хорошо, я добавляю упрощенную версию функций, обеспечивающих наблюдаемыми, может быть, это поможет что-то

func downloadAllTasks() -> Observable<Task> { 
    return Observable.create { observer in 

      //... network request to download tasks 
      //... 

      for task in tasks { 
       observer.onNext(task) 
      } 
      observer.onCompleted() 

     return Disposables.create() 
    } 
} 


func getTaskDetails(id: Int64) -> Observable<TaskDetails> { 
    return Observable.create { observer in 

     //... network request to download task details 
      //... 

     observer.onNext(taskDetails) 

     return Disposables.create() 
    } 
} 

ответ

8

С RxSwift вы хотите использовать Observable S всякий раз, когда это возможно, поэтому Я рекомендую вам реорганизовать метод downloadAllTasks для возврата Observable<Task>. Это должно быть довольно тривиально, просто пробегаем по элементам вместо испускать массива напрямую:

// In downloadAllTasks() -> Observable<Task> 
for task in receivedTasks { 
    observable.onNext(task) 
} 

Если это невозможно по каким-либо причинам, есть также оператор, который в RxSwift:

// Converts downloadAllTasks() -> Observable<[Task]> to Observable<Task> 
downloadAllTasks().flatMap{ Observable.from($0) } 

В следующем коде я буду использовать метод refactored downloadAllTasks() -> Observable<Task>, потому что это более чистый подход.

Вы можете map ваши задачи, чтобы получить их идентификатор (предполагается, что ваш Task тип имеет id: Int64 свойство) и flatMap с функцией downloadAllTasks получить Observable<TaskDetails>:

let details : Observable<TaskDetails> = downloadAllTasks() 
    .map{ $0.id } 
    .flatMap(getTaskDetails) 

Затем вы можете использовать оператор toArray() для собрать всю последовательность и испустить событие, содержащее все элементы в массиве:

let allDetails : Observable<[TaskDetails]> = details.toArray() 

Короче говоря, wi thout аннотаций типа и разделяя задачи (так что вы не будете загружать их только один раз):

let tasks = downloadAllTasks().share() 

let allDetails = tasks 
    .map{ $0.id } 
    .flatMap(getTaskDetails) 
    .toArray() 

EDIT: Обратите внимание, что эта Наблюдаемые будет ошибка, когда любой из загрузки подробно обнаруживает ошибку. Я не совсем уверен, что это лучший способ, чтобы предотвратить это, но это действительно работает:

let allDetails = tasks 
    .map{ $0.id } 
    .flatMap{ id in 
     getTaskDetails(id: id).catchError{ error in 
      print("Error downloading task \(id)") 
      return .empty() 
     } 
    } 
    .toArray() 

EDIT2: Это не сработает, если ваш getTaskDetails возвращает наблюдаемым, который никогда не завершается. Вот простая эталонная реализация getTaskDetailsString вместо TaskDetails), используя JSONPlaceholder:

func getTaskDetails(id: Int64) -> Observable<String> { 
    let url = URL(string: "https://jsonplaceholder.typicode.com/posts/\(id)")! 
    return Observable.create{ observer in 
     let task = URLSession.shared.dataTask(with: url) { data, response, error in 
      if let error = error { 
       observer.onError(error) 
      } else if let data = data, let result = String(data: data, encoding: .utf8) { 
       observer.onNext(result) 
       observer.onCompleted() 
      } else { 
       observer.onError("Couldn't get data") 
      } 
     } 
     task.resume() 

     return Disposables.create{ 
      task.cancel() 
     } 
    } 
} 
+0

Приятного и чистое объяснение, спасибо. И он работает почти так, как я хочу. Почти, beacause у меня проблема с последней частью - toArray(). Без этой части абонент уведомляется должным образом о событиях загрузки сведений о задачах. С toArray я бы ожидал, что я получу уведомление в onNext абонента, когда будут загружены все детали задачи, но onNext не вызывается вообще. Вы видите, что может быть проблемой? – Wujo

+0

@Wujo Когда при загрузке одной детали детали задается ошибка, окончательный 'Observable' будет ошибкой. Вы должны иметь возможность проверить это с помощью '.debug()' перед '.toArray()'. Я отредактировал мой ответ, если вы хотите его завершить, даже если произошла ошибка –

+0

Проблема не в ошибке во время getTaskDetails, все детали задач загружаются должным образом. Я отредактировал свое оригинальное сообщение, пытаясь указать на это более четко. – Wujo

Смежные вопросы