Swift Combine: объединение трех сигналов в один - PullRequest
2 голосов
/ 05 апреля 2020

Я имею дело с унаследованными библиотеками, где я не вправе изменять их код, и пытаюсь использовать Combine, чтобы превратить их во что-то более простое в использовании. Моя ситуация такова, что вызов метода может либо вернуть ответ, либо ответ и два уведомления. Только ответ - это сценарий успеха, ответ + 2 уведомления - это сценарий ошибки. Я хочу объединить ответ и полезную нагрузку из двух уведомлений в ошибку, которую я могу передать своему приложению. Самое интересное в том, что у меня нет гарантии, если ответ или уведомления будут на первом месте, а также то, какое уведомление будет на первом месте. Уведомления приходят в другом потоке, чем ответ. Хорошо, что они приходят «примерно в одно и то же время».

Для обработки уведомления я делаю

firstNotificationSink = notificationCenter.publisher(for: .firstErrorPart, object: nil)
  .sink { [weak self] notification in
    // parse and get information about the error
  }

secondNotificationSink = notificationCenter.publisher(for: .secondErrorPart, object: nil)
  .sink { [weak self] notification in
    // parse and get more information about the error
  }

и запрашиваю у устаревшей библиотеки ответ:

func doJob() -> String {
  let resultString = libDoStuff(reference)
}

Могу ли я использовать Combine, чтобы объединить эти три сигнала в один, то есть с периодом 50 мс? Это означает, что если я получаю результат и два уведомления, у меня есть ответ об ошибке, который я могу передать своему приложению, и если у меня есть только результат и никакие уведомления не поступили в течение 50 мс, тогда я могу передать этот успешный ответ своему приложению?

1 Ответ

1 голос
/ 05 апреля 2020

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

import UIKit
import Combine

enum Ooops : Error { case oops }

class ViewController: UIViewController {
    var storage = Set<AnyCancellable>()
    override func viewDidLoad() {
        super.viewDidLoad()
        print("start")
        NotificationCenter.default.publisher(for: Notification.Name("yoho"))
            .map {_ in true}
            .setFailureType(to: Ooops.self)
            .timeout(0.5, scheduler: DispatchQueue.main) { Ooops.oops }
            .replaceError(with: false)
            .sink {print($0)}
            .store(in: &self.storage)
        DispatchQueue.main.asyncAfter(deadline:.now()+0.2) {
            NotificationCenter.default.post(name: Notification.Name("yoho"), object: self)
        }
    }
}

Если задержка asyncAfter равна 0.2, мы получим true ( затем false, но это не важно, мы могли бы изменить это, если бы захотели). Если задержка составляет 0.9, мы получаем false. Итак, суть в том, что первое значение, которое мы получаем, правильно определяет, получили ли мы сигнал в требуемое время.

Хорошо, остальное тривиально: вы просто подключаете свои три сигнала с помощью .zip, как я уже говорил. Он генерирует кортеж после того, как все три издателя издали свой сигнал first - и это вся необходимая информация, потому что вы получили результат от вызова метода плюс Bools, которые сообщают вам поступили ли уведомления в срок. Теперь вы можете читать этот кортеж, анализировать его и делать все, что вам нравится. Оператор .zip имеет функцию отображения, поэтому вы можете выводить результаты анализа в хорошем порядке. (Если вы хотите преобразовать результат функции карты в ошибку, для этого потребуется дополнительный оператор, но опять же, это просто.)

...