Swift Combine: неожиданное противодавление с оператором zip - PullRequest
0 голосов
/ 10 января 2020

У меня есть вопрос об операторе zip в Combine в сочетании с противодавлением.

Возьмите следующий фрагмент кода:

let sequencePublisher = Publishers.Sequence<Range<Int>, Never>(sequence: 0..<Int.max)
let subject = PassthroughSubject<String, Never>()

let handle = subject
    .zip(sequencePublisher.print())
    .print()
    .sink { letters, digits in
        print(letters, digits)
    }

subject.send("a")

При выполнении этого на детской площадке, следующий вывод:

receive subscription: (0..<9223372036854775807)
receive subscription: (Zip)
request unlimited
request unlimited
receive value: (0)
receive value: (1)
receive value: (2)
receive value: (3)
receive value: (4)
receive value: (5)
receive value: (6)
receive value: (7)
...

При выполнении его на устройстве iOS код падает через несколько секунд из-за проблем с памятью.

Основная причина видна в четвертая строка выше, где zip запрашивает неограниченное количество значений из sequencePublisher. Поскольку sequencePublisher предоставляет весь диапазон значений Int, это вызывает переполнение памяти.

Что я думаю знать:

  • zip ожидает по одному значению каждого издатель, прежде чем объединить их и нажать на
  • противодавление, используется для контроля спроса от подписчика к издателю

Я ожидаю, что zip запрашивает только одно значение от каждого издатель ожидает их поступления и запрашивает следующие значения, только когда получил одно от каждого.

В этом конкретном случае я попытался построить поведение, в котором порядковый номер присваивается каждому значению, которое создается subject. Однако я могу себе представить, что это всегда проблема, когда zip объединяет значения от издателей, которые публикуют sh с очень разными частотами.

Использование противодавления в операторе zip кажется идеальным инструментом для решения этой проблемы. Вы знаете, почему это не так? Это ошибка или намеренная? Если намеренно, почему?

Спасибо, ребята

1 Ответ

1 голос
/ 10 января 2020

Кажется, что издатель Sequence просто нереалистичен c. Кажется, он не реагирует на обратное давление; он просто извергает всю последовательность сразу, что не имеет смысла в мире, где публикация должна быть асинхронной. Если вы измените Int.max на 3, проблем нет. :) Я не знаю, является ли это ошибкой или просто недостатком в целом понятии издателя Sequence.

Тем не менее, на самом деле нет проблем для вашего фактического варианта использования, потому что есть гораздо лучший способ назначьте последовательный номер каждому излучению от субъекта, а именно scan.

Вот более реалистичный c подход:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController : UIViewController {
    var storage = Set<AnyCancellable>()
    override func viewDidLoad() {
        super.viewDidLoad()
        let subject = PassthroughSubject<String, Never>()
        subject.scan(("",0)) {t,s in (s,t.1+1)}
            .sink { print($0.0, $0.1)
            }.store(in:&storage)
        delay(1) {
            subject.send("a") // a 1
            delay(1) {
                subject.send("b") // b 2
            }
        }
    }
}

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

    var storage = Set<AnyCancellable>()
    let subject = PassthroughSubject<String, Never>()
    override func viewDidLoad() {
        super.viewDidLoad()
        var counter = 1
        subject
            .sink {print($0, counter); counter += 1}
            .store(in:&storage)
        delay(1) {
            self.subject.send("a") // a 1
            self.subject.send("b") // b 2
        }
    }

...