Reactivecocoa: Substitution for SignalProducer.buffer()

Created on 20 Jul 2016  ·  11Comments  ·  Source: ReactiveCocoa/ReactiveCocoa

Deprecation warning says nothing to me - Use properties instead. 'buffer' will be removed in RAC 5.0

What properties? Could someone provide clear example. Thanks.

question

Most helpful comment

If you really need the old buffer semantics, you may create a Signal pipe, wrap it with a SignalProducer and use replayLazily.

Note that replayLazily starts caching events only if it has ever been started.

let (signal, observer) = Signal<Int, TestError>.pipe()
let replayedProducer = SignalProducer(signal: signal).replayLazily(1)

// Start the buffering immediately.
replayedProducer.start()

All 11 comments

  1. buffer(0)
    It is semantically the same as Signal.pipe, but less efficient.
  2. buffer(1)
    This is the most common use case AFAICT, and it is effectively a MutableProperty without the guarantee of always having a current value at compile time. That's why we recommend properties over buffer(1) in the deprecation message.
  3. buffer(n) for n > 1
    You may consider SignalProducer.replayLazily as the replacement, albeit the semantics being a bit different.

@andersio oh, thanks a lot, I thought not about _right_ properties

Hy,

What if I wanted to send back an error to the buffered SignalProducer?
In the MutableProperty there is NoError in the Signal or SignalProducer

Regards

If you really need the old buffer semantics, you may create a Signal pipe, wrap it with a SignalProducer and use replayLazily.

Note that replayLazily starts caching events only if it has ever been started.

let (signal, observer) = Signal<Int, TestError>.pipe()
let replayedProducer = SignalProducer(signal: signal).replayLazily(1)

// Start the buffering immediately.
replayedProducer.start()

Thanks, It's what I was looking for

Thanks,

replayLazily seems has side effect.

take a look at example below

let (signal, observer) = Signal<Int, NSError>.pipe()
let replayedProducer = SignalProducer(signal: signal).replayLazily(1)

// Start the buffering immediately.
replayedProducer.start()

func someAsyncProcedure() {
    observer.send(error: NSError())                           //<------------       LINE 2
}

func someProcedure {
    replayedProducer.startWithResult {
        switch($0) {
        case .success(let value):
           print(value)
        case .failure(let error):
           print(error)                                        //<------------      LINE 1
        }
    }
    someAsyncProcedure()
}

someProcedure() //everything is fine
someProcedure() //LINE 1 is called before LINE 2

I didn't read the code of replayLazily, but it seems used a cached value instead of using observer sent value. Problem is when observer send value, the failure procedure is called instead of success.

To solve this problem I have to create a Signal every time I call someProcedure, don't think it's a good practice... any comment is welcome

When the second time you call someProcedure, the underlying producer has already terminated with the error you sent in the first time you call someProcedure (with someAsyncProcedure).

The replayed producer cached this error for you, and this is why you get "Line 1" called before "Line 2" - it replayed the error.

Please note that sending an error means termination of the event stream.

@andersio Thanks for replying.

I guess since the producer is terminated, it's OK to create a new signal to handle the async procedure like this

class SomeClass {
    private var observer: Observer<String, NSError>!

    //delegate method async called from other thread
    func someAsyncProcedure() {
        observer.send(error: NSError())                           //<------------       LINE 2
    }

    func someProcedure -> SignalProducer<String, NSError> {
        let producer SignalProducer<String, NSError> { [weak self] sink, _ in
            self!.observer = sink
        }
        return producer
    }
}

func xxx() {
    SomeClass.someProcedure().startWithResult {
        switch($0) {
            case .success(let value):
                print(value)
            case .failure(let error):
                print(error)                                        //<------------      LINE 1
        }
    }
}

xxx()
xxx()

I guess what I want is Future semantics here...

I've read #2744 and noticed

PropertyType has the exact same semantics as SignalProducer.buffer

But as benji-bou said, Property does not emit errors. It seems I have to use two Property to handle error, one is Success Property, wrapping the value I want, the other is an Error Property, and I have to observe on both of them, right?

It seems what you want is wrapping a delegate pattern as streams of values, while the delegate is triggered as a result of starting the producer.

In this case, I think you'd be better off with an internal Signal<Result<Value, Error>, NoError> signal, and pipe your delegate callback results into it. Then you may derive producers from it.

let asyncResult: Signal<Result<Value, Error>, NoError>

func work() -> SignalProducer<Value, Error> { observer, disposable in
    // Mutual exclusion via semaphore or queues, if necessary.
    disposable += asyncResult.observeValues { result in
        switch result {
        case let .success(value):
            observer.send(value: value)
            observer.sendCompleted()
        case let .failure(error):
            observer.send(error: error)
        }
    }
    startTheAsyncWorkThatCallbacksViaDelegate()
}

@andersio Thanks for your patience, problem solved 👍

Was this page helpful?
0 / 5 - 0 ratings