Добавляйте элемент к издателю, только если он хочет закончить без публикации какого-либо значения

У меня есть два издателя, каждый из которых может публиковать ценность или полную версию без публикации вообще. Я объединяю двух издателей и сравниваю их значения, выполняю некоторую пост-обработку значений и сохраняю их в моем локальном CoreData. Упрощенный код выглядит так:

let p1 = ["1"]
    .publisher
    .map { Int($0) }

let p2 = ["2"]
    .publisher
    .map { Int($0) }

let p1p2 = p1.combineLatest(p2)
    .map { $0 == $1 }
    .sink { print($0) }

Но в случае, если один из издателей не публикует какое-либо значение, мне все равно нужно сохранить оставшееся значение в моей локальной БД. Проблема в том, что оператор combLatest не запускает никакого события, если один из издателей завершает работу, не запустив значение. Я пробовал операторы добавления и добавления, но их нельзя комбинировать с какими-либо условиями, например, если один из издателей закончит, не опубликовав какое-либо значение.

Например, что-то вроде:

let p1String: [String] = []
let p1 = p1String.publisher
    .map { Int($0) }
    .if(completionWithoutPublishing, perform: { prepend(nil) })

Любые идеи поощряются.


person Aswath    schedule 06.04.2021    source источник
comment
Если он уже завершен, вы не можете добавить к нему начало, не так ли?   -  person Sweeper    schedule 06.04.2021
comment
Это причина моего вопроса. Я не знаю, можно ли это сделать, но если с помощью какой-то магии оператора я могу добавить только тогда, когда он увидит завершение, не увидев значения, мои проблемы будут легко решены   -  person Aswath    schedule 06.04.2021


Ответы (2)


Вы ищете оператора replaceEmpty:

https://developer.apple.com/documentation/combine/publishers/zip4/replaceempty(with:)

https://www.apeth.com/UnderstandingCombine/operators/operatorsTransformersBlockers/operatorsreplaceempty.html

Он делает именно то, что вы описываете: он выдает свое особое значение только в том случае, если восходящий поток завершается без публикации.

person matt    schedule 06.04.2021

Вы можете создать своего собственного оператора, который сделает это. Вам нужно только реализовать receive(subscriber:). Вы делаете это, добавляя свою собственную логику к вышестоящему издателю (в вашем случае map), а затем присоединяете к нему параметр подписчика.

struct IfEmpty<Upstream: Publisher>: Publisher {
    let upstream: Upstream
    let output: Output
    let handler: (() -> Void)?
    
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input {
        var hasOutput = false
        upstream.handleEvents(receiveOutput: { (_) in
            hasOutput = true
        }, receiveCompletion: { (_) in
            if !hasOutput {
                subscriber.receive(output)
                handler?()
            }
        }).receive(subscriber: subscriber)

    }
    
    typealias Output = Upstream.Output
    
    typealias Failure = Upstream.Failure
}

extension Publisher {
    func ifEmpty(publish output: Output, andDo handler: (() -> Void)? = nil) -> IfEmpty<Self> {
        IfEmpty(upstream: self, output: output, handler: handler)
    }
}

Примеры:

let p1String: [String] = []
let p1 = p1String.publisher
    .map { Int($0) }
    .ifEmpty(publish: nil)
    .print()
    .sink(receiveValue: { _ in })
/*
receive subscription: (HandleEvents)
request unlimited
receive value: (nil)
receive finished
*/
let p1String: [String] = ["1", "2", "3"]
let p1 = p1String.publisher
    .map { Int($0) }
    .ifEmpty(publish: nil)
    .print()
    .sink(receiveValue: { _ in })
receive subscription: (HandleEvents)
request unlimited
receive value: (Optional(1))
receive value: (Optional(2))
receive value: (Optional(3))
receive finished
*/
person Sweeper    schedule 06.04.2021
comment
Уже есть встроенный оператор replaceEmpty(with:) - person New Dev; 06.04.2021
comment
@NewDev, извините, я не видел вашего комментария, пока не написал свой ответ. - person matt; 06.04.2021
comment
@NewDev Gosh. Бог знает, как я это пропустил, когда просматривал список ... - person Sweeper; 06.04.2021
comment
@Sweeper Но вы получите высшую оценку за изобретательность! - person matt; 06.04.2021
comment
@Aswath Все в порядке :) Я добавил необязательный параметр handler, чтобы немного отличаться от replaceEmpty, вдохновленный синтаксисом закрытия в вашей попытке. - person Sweeper; 06.04.2021