2016-12-01 11 views
5

Jodły w całości Jestem nowy w rxswift, więc myślę, że odpowiedź jest oczywista, ale w tej chwili nie mogę znaleźć rozwiązania samodzielnie.Właściwe użycie RxSwift do żądania łańcuchów, flatMap lub czegoś innego?

Mam dwie funkcje:

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

Pierwszy pobiera listę Task obiektów za pomocą żądania sieciowego, drugi pobierając szczegóły zadania dla sepcific zadania (za pomocą jego id)

Czego chcę od Osiągnięcie polega na pobraniu wszystkich zadań, a następnie dla każdego zadania chcę pobrać jego szczegóły i zasubskrybować zdarzenie uruchamiane, gdy wszystkie szczegóły zadań są gotowe.

Sądzę, że powinienem zapisać się do Observable < [TaskDetails]>, ale nie wiem jak to zrobić.

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

// EDIT

Dzięki Silvan Mosberger odpowiedzi jestem znacznie bliżej do rozwiązania. Pozostał tylko jeden problem. Teraz mam coś takiego:

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) 

wyjście jest

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

Istnieją 3 Zadania dostępne tak jak można se wszystkie z nich są pobierane prawidłowo jednak z jakiegoś powodu wyniku ToArray() - (Observable<[TaskDetails]>) nie generuje "onNext", gdy wszystkie szczegóły zadania są gotowe.

// Edit raz

Ok, Dodaję uproszczoną wersję funkcji zapewniających obserwable, może to pomoże coś

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

Odpowiedz

8

Z RxSwift chcesz użyć Observable s gdy jest to możliwe, dlatego Polecam, aby zmienić metodę downloadAllTasks, aby zwrócić wartość Observable<Task>. To powinno być dość trywialne tylko przez zapętlenie przez elementy zamiast emitowania tablicy bezpośrednio:

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

Jeżeli nie jest to możliwe, niezależnie od przyczyny, nie ma też operator że RxSwift:

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

W poniższym kodzie użyję metody refaktoryzowanej downloadAllTasks() -> Observable<Task>, ponieważ jest to podejście czystsze.

Można wtedy map Twoje zadania, aby ich id (zakładając, że Twój typ Task ma właściwość id: Int64) i flatMap z funkcją downloadAllTasks uzyskać Observable<TaskDetails>:

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

Następnie można użyć operatora toArray() do Zbierz całą sekwencję i wyślij zdarzenie zawierające wszystkie elementy w tablicy:

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

Krótko mówiąc, wi thout adnotacje typu i dzielić zadania (więc nie będzie pobierać je tylko raz):

let tasks = downloadAllTasks().share() 

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

EDIT: Zauważ, że ten Obserwowalne będzie błąd, gdy którykolwiek z pobrania szczegółowo napotka błąd. Nie jestem do końca pewien, co jest najlepszym sposobem, aby tego uniknąć, ale to działa:

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

EDIT2: To nie zadziała, jeśli getTaskDetails zwraca się zaobserwować, że nigdy nie kończy. Oto prosta implementacja referencyjna getTaskDetails (z String zamiast TaskDetails), stosując 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

Nice and clean wyjaśnienie, dziękuję. I prawie działa tak, jak chcę. Prawie, ponieważ mam problem z ostatnią częścią - toArray(). Bez tej części subskrybent jest prawidłowo powiadamiany o zdarzeniach pobierania szczegółów zadań. Z toArray oczekiwałbym, że otrzymam powiadomienie w onNext subskrybenta po pobraniu wszystkich szczegółów zadania, ale onNext nie jest w ogóle wywoływany. Czy widzisz, co może być problemem? – Wujo

+0

@Wujo Gdy pobieranie pojedynczego zadania zostanie napotkane podczas błędu, ostateczny błąd "Observable" spowoduje błąd. Powinieneś być w stanie to sprawdzić za pomocą '.debug()' przed '.toArray()'. Edytowałem moją odpowiedź, jeśli chcesz ją ukończyć, mimo że wystąpił błąd –

+0

Problem nie jest błędny podczas getTaskDetails, wszystkie szczegóły zadań są pobierane poprawnie. Zmieniłem swój oryginalny post, próbując wskazać to dokładniej. – Wujo

Powiązane problemy