How do I emit events manually (i.e., make an EventEmitter) using RxSwift? - swift

How do I implement an event emitter using RxSwift? (An object that can emit data that is consumed by other objects that are subscribed to it.)
After going through the Rx docs and examples, I feel like a complete idiot and am still extremely confused on how to manually emit events from Observers to Observables. My understanding that we have some Observable that can emit events with data to all Observers that are subscribed to that Observable. However, I have zero idea on how this is actually implemented in Swift.
Here's an example of something I'm trying to implement:
class VendingMachine {
let dispenser = Observable<Drink>
// Notify all subscribed Observers that this machine dispensed a drink.
func dispenseDrink(item: Drink) {
dispenser.onNext(item)
}
}
And a second file:
class MachineReporter: Observer {
let dispenser = VendingMachine().dispenser
init() {
dispenser.subscribe(self)
}
onNext { drink in
print("Vending machine dispensed a drink: \(drink)"
}
}
My brain is fried. I'm just going to switch to a specialized library like EmitterKit for now because I'm clearly misunderstanding how this works.
But I need to figure out how Rx works or I will go crazy. Help!

I'm pretty confused about what you're planning on doing with MachineReporter, so my code just addresses making it work, although it may not be very useful.
I would highly suggest you go through the RxSwift Playground examples and make sure you feel comfortable with those. Your question in particular deals with Subjects. That should get you over the initial "hump" of uncomfortability and frustration. I definitely hear you about that though, it's a different way of thinking, but it's completely worth pushing past that first hurdle. Stick with it.
class VendingMachine {
let dispenser = PublishSubject<Drink>()
func dispenseDrink(item: Drink) {
dispenser.onNext(item)
}
}
class MachineReporter {
let disposeBag = DisposeBag()
let dispenser = VendingMachine().dispenser
init() {
dispenser.asObservable()
.subscribeNext { drink in
print("Vending machine dispensed a drink: \(drink)")
}
.addDisposableTo(disposeBag)
}
}

Should use a Subject for this.
A Subject is a special form of an Observable Sequence, you can
subscribe to and add elements to it. There are 4
kinds of Subjects in RxSwift
http://reactivex.io/documentation/subject.html
I would recommend starting off using a PublishSubject here.
let observable = PublishSubject<SomeTypeOrEvent>()
observable.subscribe(onNext: { (SomeTypeOrEvent) in
self.doSomethingWithEvent(something: SomeTypeOrEvent)
}).addDisposableTo(disposeBag)
...
// emits some type/event
observerable.onNext(SomeTypeOrEvent)

Related

In a Combine Publisher chain, how to keep inner objects alive until cancel or complete?

I've created a Combine publisher chain that looks something like this:
let pub = getSomeAsyncData()
.mapError { ... }
.map { ... }
...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
return wsi.subject
}
.share().eraseToAnyPublisher()
It's a flow of different possible network requests and data transformations. The calling code wants to subscribe to pub to find out when the whole asynchronous process has succeeded or failed.
I'm confused about the design of the flatMap step with the WebSocketInteraction. That's a helper class that I wrote. I don't think its internal details are important, but its purpose is to provide its subject property (a PassthroughSubject) as the next Publisher in the chain. Internally the WebSocketInteraction uses URLSessionWebSocketTask, talks to a server, and publishes to the subject. I like flatMap, but how do you keep this piece alive for the lifetime of the Publisher chain?
If I store it in the outer object (no problem), then I need to clean it up. I could do that when the subject completes, but if the caller cancels the entire publisher chain then I won't receive a completion event. Do I need to use Publisher.handleEvents and listen for cancellation as well? This seems a bit ugly. But maybe there is no other way...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
self.currentWsi = wsi // store in containing object to keep it alive.
wsi.subject.sink(receiveCompletion: { self.currentWsi = nil })
wsi.subject.handleEvents(receiveCancel: {
wsi.closeWebSocket()
self.currentWsi = nil
})
Anyone have any good "design patterns" here?
One design I've considered is making my own Publisher. For example, instead of having WebSocketInteraction vend a PassthroughSubject, it could conform to Publisher. I may end up going this way, but making a custom Combine Publisher is more work, and the documentation steers people toward using a subject instead. To make a custom Publisher you have to implement some of things that the PassthroughSubject does for you, like respond to demand and cancellation, and keep state to ensure you complete at most once and don't send events after that.
[Edit: to clarify that WebSocketInteraction is my own class.]
It's not exactly clear what problems you are facing with keeping an inner object alive. The object should be alive so long as something has a strong reference to it.
It's either an external object that will start some async process, or an internal closure that keeps a strong reference to self via self.subject.send(...).
class WebSocketInteraction {
private let subject = PassthroughSubject<String, Error>()
private var isCancelled: Bool = false
init() {
// start some async work
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if !isCancelled { self.subject.send("Done") } // <-- ref
}
}
// return a publisher that can cancel the operation when
var pub: AnyPublisher<String, Error> {
subject
.handleEvents(receiveCancel: {
print("cancel handler")
self.isCancelled = true // <-- ref
})
.eraseToAnyPublisher()
}
}
You should be able to use it as you wanted with flatMap, since the pub property returned publisher, and the inner closure hold a reference to self
let pub = getSomeAsyncData()
...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
return wsi.pub
}

RxSwift: compactMap never executed

I'm trying to implement compactMap on RxSwift but it seems like is never executed.
Here is my code:
class MyClass{
var disposeBag = DisposeBag()
let subject = BehaviorRelay(value: 1)
func doSomething() {
Observable.from(optional: subject).compactMap{ $0
}.subscribe( onNext:{
print($0)
}).disposed(by: disposeBag)
subject.accept(2)
subject.accept(4)
subject.accept(5)
subject.accept(8)
}
}
When I change the value on subject the compactMap never gets called. Why not?
You are creating an Observable<BehaviorRelay<Int>> by using the from operator which only emits one value (the behavior relay itself) and then completes. The accept calls are being ignored because nothing is subscribing to the behavior relay itself.
I think you need to step back and figure out what you are trying to accomplish, and then read the documentation on the operators to find one that does what you need.

NotificationCenter.Publisher VS PassThroughSubject

I have an object which I want to send throughout multiple listeners/subscribers, so I was checking out Combine and I saw 2 different kind of publishers, namely NotificationCenter.Publisher and PassThroughSubject. I am confused why anyone would use a NotificationCenter.Publisher over PassThroughSubject.
I came up with the code below, demonstrating both ways. To summarize:
NotificationCenter.Publisher needs to have a Notification.Name static property
Isn't really that typesafe (since I can post a different kind of object for the same Notification.Name/different publisher for the same Notification.Name)
Posting a new value needs to be done on NotificationCenter.default (not the publisher itself)
An explicit downcast to the used type in the map closure
In what scenarios someone will use NotificationCenter.Publisher over PassThroughSubject?
import UIKit
import Combine
let passThroughSubjectPublisher = PassthroughSubject<String, Never>()
let notificationCenterPublisher = NotificationCenter.default.publisher(for: .name).map { $0.object as! String }
extension Notification.Name {
static let name = Notification.Name(rawValue: "someName")
}
class PassThroughSubjectPublisherSubscriber {
init() {
passThroughSubjectPublisher.sink { (_) in
// Process
}
}
}
class NotificationCenterPublisherSubscriber {
init() {
notificationCenterPublisher.sink { (_) in
// Process
}
}
}
class PassThroughSubjectPublisherSinker {
init() {
passThroughSubjectPublisher.send("Henlo!")
}
}
class NotificationCenterPublisherSinker {
init() {
NotificationCenter.default.post(name: .name, object: "Henlo!")
}
}
If you have to use a 3rd party framework that uses NotificationCenter.
NotificationCenter can be thought of as a first generation message passing system, while Combine is second generation. It has runtime overhead and requires casting the objects you can store in Notifications. Personally I would never use NotificationCenter when building an iOS 13 framework, but you do need to use it to access a lot of iOS notifications that are only published there. Basically in my personal projects I’m going to treat it as read only unless absolutely necessary.

Dealing with Swift 5 Exclusivity Enforcement when using Combine

Swift 5, the "Exclusive Access to Memory" enforcement is now on by default for release builds as mentioned in this Swift.org blog post:
Swift 5 Exclusivity Enforcement
I understand the reasoning behind this feature, but with the new Combine framework I feel as if some very normal design patterns are now going to break and I'm curious how best to work around them.
With Combine it's natural for parts of your code to react to changes in a model such that they might need to read from the very property that the model has just changed. But they can no longer do that because it will trigger a memory exception as you attempt to read a value that is currently being set.
Consider the following example:
struct PasswordProposal {
let passwordPublisher = CurrentValueSubject<String, Never>("1234")
let confirmPasswordPublisher = CurrentValueSubject<String, Never>("1234")
var password:String {
get { passwordPublisher.value }
set { passwordPublisher.value = newValue }
}
var confirmPassword:String {
get { confirmPasswordPublisher.value }
set { confirmPasswordPublisher.value = newValue }
}
var isPasswordValid:Bool {
password == confirmPassword && !password.isEmpty
}
}
class Coordinator {
var proposal:PasswordProposal
var subscription:Cancellable?
init() {
self.proposal = PasswordProposal()
self.subscription = self.proposal.passwordPublisher.sink { [weak self] _ in
print(self?.proposal.isPasswordValid ?? "")
}
}
// Simulate changing the password to trigger the publisher.
func changePassword() {
proposal.password = "7890"
}
}
// --------------------------------
var vc = Coordinator()
vc.changePassword()
As soon as changePassword() is called, the mutual exclusivity enforcement will throw an exception because the property password will attempt to be read from while it's currently being written to.
Note that if you change this example to use a separate backing storage property instead of the CurrentValueSubject it causes the same exception.
However, if you change PasswordProposal from being a struct to a class, then the exception is no longer thrown.
When I consider how I might use Combine in an existing codebase, as well as in SwiftUI, I see this type of pattern coming up in a lot of places. In the old delegate model, it's quite common for a delegate to query the sending object from within a delegate callback. In Swift 5, I now have to be very careful that none of those callbacks potentially read from the property that initiated the notification.
Have others come across this and, if so, how have you addressed it? Apple has routinely suggested that we should be using structs where it makes sense but perhaps an object that has published properties is one of those areas where it doesn't?
The password property is not the problem. It's actually the proposal property. If you add a didSet property observer to proposal, you'll see it's getting reset when you set password, then you access self?.proposal from within your sink while it's being mutated.
I doubt this is the behavior that you want, so it seems to me like the correct solution is to make PasswordProposal a class.

RxSwift - Who is the Observer?

I've just started studying Rxswift lately. And there's this one question that's been bothering me since. Take this code snippet for example:
class MyClass {
var v = Variable("")
var bag = DisposeBag()
func subscribe() {
let ob = v.asObservable()
ob.subscribe(onNext: { (value) in
print("Value changed: " + value)
}).disposed(by: bag)
}
}
What bothers me is, where/who is the real observer in the scenario of subscribe() method? In term of objects, here we have ob who acts as an observable, but I can't really see the observer object anywhere.
Can anyone pls brighten my mind?
The observer in the example above is really the closure which you provide to the subscribe(onNext:) function.
Of course, how long that closure sticks around is determined by the lifetime of your DisposeBag: when your instance of MyClass dies, bag dies, and therefore the closure dies. For this reason, you may find people calling your instance of MyClass the "observer".