swiftobservablerx-swiftcocoaasyncsocket

Correct usage of PublishSubject with CocoaAsyncSocket


I'm creating simple messaging app based on CocoaAsyncSocket and STOMP protocol. So I created main class, which use PublishSubject, so the subscribers can observe incoming STOMP frames, something like this:

class StompStream: NSObject {
//MARK: - Public
     var inputFrame = PublishSubject<StompFrame>()

//MARK: - Private
    fileprivate var socket: GCDAsyncSocket!
    ...
    ...
    fileprivate func parse(withData data: Data) {
        let string = String(data: data, encoding: .utf8)
        do {
            let frame = try StompFrame(text: string)
            self.inputFrame.on(.next(frame))
        } catch {
            self.inputFrame.on(.error(error))
        }
     }
 }
//MARK: - GCDAsyncSocketDelegate methods
extension StompStream: GCDAsyncSocketDelegate {
    ...
    ...
    func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
        self.parse(withData: data)
    }
}

So, other who want to read inputFrame, just do:

fileprivate func subscribeToEvents() {
    self.stompStream.inputFrame
        .observeOn(SerialDispatchQueueScheduler(queue: Queues().stompQueue, internalSerialQueueName: "stomp"))
        .subscribe( onNext: { inputFrame in
            //do some stuff
        }).addDisposableTo(self.disposeBag)
}

I'm very new to RxSwift, and I read, that it's better to use Observable<StompFrame>, if we want to just read the events, but with PublishSubject I can init it like this - PublishSubject<StompFrame>(), and with Observable<StompFrame> I couldn't understand how to do such thing, without creating some func, where I will return Observable.create { }, so every time if somebody will want to receive events, it will create another Observable<StompFrame>, but with PublishSubject it's only one. Thanks for any help to explaining me this Rx stuff :)


Solution

  • Because the code is bridging from imperative to reactive world, here it makes sense to use a PublishSubject under the hood.

    A good practice is to make this subject private and only expose the Observable to outside users.

    class StompStream: NSObject {
        var inputFrame: Observable<StompFrame> { 
            return inputFrameSubject.asObservable() 
        }
    
        private let inputFrameSubject = PublishSubject<StompFrame>()
    
        // ...
    }
    

    When in doubt about using a subject or not, I always refer to this post. Though written for C#, it is a great reference for this topic.