RxSwift Using Variables Correctly

11,933

Hmm... Here is an article about using Variable (note that Variable is a wrapper around BehaviorSubject.)

http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx

In your case, you already have a cold observable (the network call,) so you don't need a Subject/Variable. All you need to do is publish the observable you already have and use replay(1) to cache the value. I would expect a class named something like ParseServer that contains a computed property named something like mushrooms.

To help get the mushrooms out of parse, you could use this (this will create the cold observable you need):

extension PFQuery {

    var rx_findObjects: Observable<[PFObject]> {
        return Observable.create { observer in
            self.findObjectsInBackgroundWithBlock({ results, error in
                if let results = results {
                    observer.on(.Next(results))
                    observer.on(.Completed)
                }
                else {
                    observer.on(.Error(error ?? RxError.Unknown))
                }
            })
            return AnonymousDisposable({ self.cancel() })
        }
    }

}

And then you would have something like:

class ParseServer {
    var mushrooms: Observable<[Mushroom]> {
        return PFQuery(className: "Mushroom").rx_findObjects
        .map { $0.map { Mushroom(pfObject: $0) } }
        .publish()
        .replay(1)
    }
}

I think the above is correct. I didn't run it through a compiler though, much less test it. It might need editing.

The idea though is that the first time you call myParseServer.mushrooms the system will call Parse to get the mushrooms out and cache them. From then on, it will just return the previous cashed mushrooms.

Share:
11,933
Manny
Author by

Manny

Updated on July 20, 2022

Comments

  • Manny
    Manny almost 2 years

    I am trying to convert a project to use RxSwift and MVVM. I have a service that syncs a list of data from Parse on every app launch and I basically want to make sure I am taking the correct approach.

    What I have done is made a Variable subject and then allow my models to listen to this. ParseService:

    let rx_parseMushrooms = Variable<[ParseMushroom]>([])
    

    MushroomLibraryModel:

    _ = parseService.rx_parseMushrooms
        .asObservable()
        .map { (parseMushrooms:[ParseMushroom]) -> [Mushroom] in
            let mushrooms = parseMushrooms.map { (parseMushroom:ParseMushroom) -> Mushroom in
                let mushroom = Mapper<Mushroom>().map(parseMushroom.dictionaryWithValuesForKeys(parseMushroom.allKeys()))
                return mushroom!
            }
    
            return mushrooms
        }
        .subscribeNext({ (mushrooms:[Mushroom]) -> Void in
            self.mushrooms = mushrooms
            print(mushrooms)
        })
    

    I do the same for expressing the sync state.

    ParseService:

    struct SyncState {
        enum State {
            case Unsynced, ConnectingToServer, SyncingInfo, FetchingImageList, SyncingImages, SyncComplete, SyncCompleteWithError
        }
    
        var infoToSync = 0
        var imagesToSync = 0
        var imagesSynced = 0
    
        var state = State.Unsynced
    }
    
    let rx_syncState = Variable(SyncState())
    

    I then update the variable a la

    self.rx_syncState.value = self.syncState
    

    SyncViewModel:

    _ = parseService.rx_syncState
         .asObservable()
         .subscribeNext { [weak self] (syncState:ParseService.SyncState) -> Void in
              switch syncState.state {
                  //show stuff based on state struct
              }
          }
    

    Anyways, I would greatly appreciate if someone can tell me if this is a good way of going about it or if I am misusing RxSwift (and guide me on how I should be doing this).

    Cheers!

  • Manny
    Manny about 8 years
    Awesome and thanks for the reply :) What about for the sync state part? Basically I have a service class that does a bunch of stuff until the library is synced. This "syncLibrary()" method gets called on app launch but I want several classes to be able to listen to the state of sync in case new mushrooms become available. I also have the initial screen in which I show the progress of sync. Would you also make syncLibrary a var? This is the app btw in case it explains better what I do goo.gl/xJaV0d it's already out I'm just trying to master RxSwift it keeps blowing my mind..
  • Daniel T.
    Daniel T. about 8 years
    Cool looking app. Like the article I referenced says, you rarely need subjects (including variable.) I think they are mainly in the library for those times when you have something that for example takes a delegate instead of a call back and you want to convert that into an rx stream.
  • Daniel T.
    Daniel T. about 8 years
    In the case of syncing, your DB code should already be wrapped so that it returns observables. Kind of like what I did above for PFObject. Subjects are for generating hot observables from nothing. Your DB should not be generating hot observables.
  • Manny
    Manny about 8 years
    Yeah. I guess I'm still wrapping my head around hot vs cold observables. This makes sense. Thanks again
  • Daniel T.
    Daniel T. about 8 years
    Easy way to think about it. When you subscribe to a hot observable, all it does is add the observer to its list of observers. When you subscribe to a cold observable, it adds the observer to the list and starts some sort of process. So for example UIButton.rx_tap is hot, PFQuery.rx_findObjects in my code above is cold (a new server call is made for every subscription.)