Migrate from Racsignal to Reactiveswift or Rac5

Migrate from RACSignal to ReactiveSwift or RAC5

The important thing to understand about Signal and SignalProducer is the distinction between Hot and Cold Signals.

Basically, a Hot Signal is one that does not care about its Observers. It sends its values, no matter if it has one, multiple or even no observer at all. And most important: New observations do not cause side effects in the signal and each new subscriber will get the exact same events as the other subscribers (minus the ones that have already occurred before the subscription!)! Think things like user input, sensor data, ... (ignoring things like starting/stopping the sensor).

In my experience, real Hot Signals are rare in practice..

In contrast, a Cold Signal is one that cares about its Observers - each subscription to a Cold Signal potentially performs a side effect and the subscriber receives events based on that side effect. So two different observers each start the side effect once and get distinct sets of events.

In RAC, Cold Signals are represented by SignalProducer. You might also think of a SignalProducer as a Factory for Signals (hence the name) - starting a SignalProducer performs the side effect and returns a Signal on which the events are sent.

Thats pretty much what your snippet does.

Disposable has not changed much since RAC 2.x, you can still use that. You probably have just missed how to use it when creating a SignalProducer:

func producerForGET(urlString: String, parameters: [String: String]) -> SignalProducer<Data, NSError> {
return SignalProducer<Data, NSError> { observer, disposable in
let operation = GET(url: urlString, parameters: parameters, success: { operation, responseObject in
observer.send(value: responseObject)
observer.sendCompleted()
}, failure: { error in
observer.send(error: error)
})

disposable += {
print("Disposed")
operation.cancel()
}
}
}

Here's a quick example how to use this can be used:

producerForGET(urlString: "Bla", parameters: [:])
.start() // Performs the request and returns a Signal
.dispose() // Runs the disposable block and cancels the operation

Reactive Cocoa 5 and ReactiveSwift network requests handling

The "Making network requests" example from the ReactiveSwift readme is a good example for this type of thing. Rather than using observeValues on your text field signal, typically you would use .flatMap(.latest) to hook it up directly to your SignalProducer like so (note I haven't checked this code, but hopefully it gets the idea across):

self.textField.reactive.continuousTextValues
.skipNil()
.filter { (value) -> Bool in
return value.characters.count > 0
}
.flatMap(.latest) { [unowned self] value in
return self.producerFor(keyword: value)
// Handling the error here prevents errors from terminating
// the outer signal. Now a request can fail while allowing
// subsequent requests to continue.
.flatMapError { error in
print("Network error occurred: \(error)")
return SignalProducer.empty
}
}
.observe(on: UIScheduler())
.observe { [unowned self] event in
switch event {
case let .value(items):
print("value")
self.items.append(contentsOf: items)
self.tableView.reloadData()

case let .failed(error):
print("error")

case .completed, .interrupted:
print("completed")
}
}

Specifying .latest causes the previous network request to automatically be cancelled when a new one starts, so there's no need to keep track of the current request in a global variable.

As for managing lifetime, it's hard to say what the best thing is without knowing your broader code structure. Typically I would add something like .take(during: self.reactive.lifetime) to my signal to terminate the subscription when self is deallocated, probably right before the call to observe.

Error events terminate signals. There's no need to send a completed event after an error, and observers won't see it anyway. Basically, complete indicates that the signal terminated successfully while an error indicates that the signal terminated in failure.

How do I convert a RACSignal to a SignalProducer in ReactiveCocoa 5?

Use bridgedSignalProducer() in ReactiveObjCBridge:

someSignal.toSignalProducer()

becomes

bridgedSignalProducer(from: someSignal)

This produces a SignalProducer<Value?, AnyError>. Unlike RAC 4's startWithNext(), RAC 5's startWithValues() is only on SignalProducers whose Error type is NoError. To get around this, I added a utility function on SignalProducer that behaves the same way as startWithValues but works with any Error type (ignoring any error.)

Create a RACSignal which sends error if RACSignal sends next

More idiomatic would be:

RACSignal *crashSignal = [cancelSignal flattenMap:^(id value) {
return [RACSignal error:[self createError]];
}];

How to get error value from a subscribed RACSignal?

The syntax you use makes me think you're using Swift, in which case you shouldn't be using RACSignal, you should be converting your RACSignal to a SignalProducer with .toSignalProducer()

func sendSuccessOrNot() -> RACSignal {
return RACSignal.createSignal { (subscriber) -> RACDisposable! in
let test = true
if (test) {
subscriber.sendNext("Value")
subscriber.sendCompleted()
} else {
subscriber.sendError(NSError(domain: "", code: 0, userInfo: nil))
}
return RACDisposable(block: {})
}
}

controller.sendSuccessOrNot().toSignalProducer().on(next: { value in
print("next: \(value)")
},
failed: { error in
print("failed: \(error)")
}).start()

If you're still using RAC 2 then it'd be

controller.sendSuccessOrNot().subscribeNext({ value in
print("next: \(value)")
}, error: { error in
print("failed: \(error)")
})

How to zip RACObserve with return correctly?

Glad you managed to solve it for your use case but just in case someone wants to zip two signals and also have a return come through with them, you can use repeat.

Like so: [RACSignal zip:@[signal1, [RACSignal return:@YES].repeat, signal2]];

Reactive Cocoa / Reactive Swift - Swift 3.0 missing methods

Some parts of the Obj-C API have been divided in another framework : ReactiveObjC.

I needed to install this framework to access these methods.

Solution :

As stated in README (Objective-C and Swift section), those Objective-C
API are splitted out to ReactiveObjC framework. You need to add
https://github.com/ReactiveCocoa/ReactiveObjC as a submodule, link the
framework, then import ReactiveObjC.

Please see the following discussion on the issue :

https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3197

Create a RACSignal from rac_GET after pushing a button, cancelling previous requests

This seems to work:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = nil;
RACSignal *buttonSignal = [self.stepperButton rac_signalForControlEvents:UIControlEventTouchUpInside];
RACSignal *getSignal = [buttonSignal map:^id(UIButton *button) {
return [[manager rac_GET:@"https://farm3.staticflickr.com/2815/13668440264_e6403b3100_o_d.jpg" parameters:nil] catch:^RACSignal *(NSError *error) {
NSLog(@"catch %@",error);
return [RACSignal empty];
}];
}];
RACSignal *latestSignal = [getSignal switchToLatest];
[latestSignal subscribeNext:^(NSData *data) {
NSLog(@"dowloaded %d bytes",data.length);
}];

Thanks Stackoverflow similar questions! Powerful stuff.

The catch should be on the rac_GET. Before I had been trying to do things with catch but on the buttonSignal pipe.

And the reason x was always null was because I did not have a serializer set up on the manager.

I thought about deleting this question, but maybe there are people that still have remarks on the solution?



Related Topics



Leave a reply



Submit