Как в цепочке Combine Publisher поддерживать внутренние объекты до отмены или завершения? - PullRequest
0 голосов
/ 21 июня 2020

Я создал цепочку издателей Combine, которая выглядит примерно так:

let pub = getSomeAsyncData()
           .mapError { ... }
           .map { ... }
           ...
           .flatMap { data in 
               let wsi = WebSocketInteraction(data, ...)
               return wsi.subject
           }
           .share().eraseToAnyPublisher()

Это поток различных возможных сетевых запросов и преобразований данных. Вызывающий код хочет подписаться на pub, чтобы узнать, когда весь асинхронный процесс завершился успешно или потерпел неудачу.

Меня смущает конструкция шага flatMap с WebSocketInteraction. Я написал это вспомогательный класс. Я не думаю, что его внутренние детали важны, но его цель - предоставить свойство subject (a PassthroughSubject) в качестве следующего издателя в цепочке. Внутренне WebSocketInteraction использует URLSessionWebSocketTask, общается с сервером и публикует на subject. Мне нравится flatMap, но как сохранить этот фрагмент в течение всего срока существования цепочки Publisher?

Если я сохраню его во внешнем объекте (без проблем), то мне нужно его очистить. Я мог бы сделать , что , когда subject завершится, но если вызывающий отменит всю цепочку издателя, я не получу событие завершения. Нужно ли мне использовать Publisher.handleEvents и также ожидать отмены? Это кажется немного некрасивым. Но, может быть, другого пути нет ...

.flatMap { data in 
    let wsi = WebSocketInteraction(data, ...)
    self.currentWsi = wsi  // store in containing object to keep it alive.
    wsi.subject.sink(receiveCompletion: { self.currentWsi = nil })
    wsi.subject.handleEvents(receiveCancel: {
        wsi.closeWebSocket()
        self.currentWsi = nil
    })

У кого-нибудь есть здесь хорошие «шаблоны проектирования»? Например, вместо WebSocketInteraction vend a PassthroughSubject он может соответствовать Publisher. Я могу пойти по этому пути, но создание собственного Combine Publisher - это больше работы, а документация побуждает людей использовать вместо этого предмет. Чтобы создать настраиваемого издателя, вам необходимо реализовать некоторые из вещей, которые PassthroughSubject делает за вас, например, реагирование на запрос и отмену, а также сохранение состояния, чтобы гарантировать, что вы завершите работу не более одного раза и не отправите события после этого.

[Изменить: чтобы уточнить, что WebSocketInteraction - мой собственный класс.]

1 Ответ

0 голосов
/ 22 июня 2020

Не совсем понятно, с какими проблемами вы сталкиваетесь при сохранении живого внутреннего объекта. Объект должен быть живым, пока что-то имеет сильную ссылку на него. via self.subject.send(...).

class WebSocketInteraction {
   private let subject = PassthroughSubject<String, Error>()

   private var isCancelled: Bool = false

   init() {
      // start some async work
      DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
         if !isCancelled { self.subject.send("Done") } // <-- ref
      } 
   }

   // return a publisher that can cancel the operation when
   var pub: AnyPublisher<String, Error> {
      subject
         .handleEvents(receiveCancel: {
             print("cancel handler")
             self.isCancelled = true  // <-- ref
         })
         .eraseToAnyPublisher()
   }
}

Вы должны иметь возможность использовать его по своему усмотрению с flatMap, так как свойство pub возвращает publisher, а внутреннее замыкание содержит ссылку на self

let pub = getSomeAsyncData()
           ...
           .flatMap { data in 
               let wsi = WebSocketInteraction(data, ...)
               return wsi.pub
           }
...