Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save cliss/51cb740b14f3cd56ba1d11f2a9a6ba02 to your computer and use it in GitHub Desktop.
Save cliss/51cb740b14f3cd56ba1d11f2a9a6ba02 to your computer and use it in GitHub Desktop.
//a somewhat contrived example of how reactive programming can be useful
//some real concerns that are omitted, but can be easily accomplished using rxswift are
//error handling, displaying loading indicators, etc
// A result object that comes from the network.
// The contents are irrelevant for this example.
struct Result {
let text: String
let someOtherThing: String
}
// An object that can get data from the network
struct Fetcher {
/**
Performs a network query, and returns 0 or more results as an array.
- Parameter query: The query to complete. Comes from the UI
- Returns: An observable with 0 or more results
*/
static func performQuery(query: String) -> Observable<[Result]> {
// Go to the network
// Get data
// Transform into Result objects
// If the returned Observable is disposed, it will cancel the network request.
Observable.create { observer in
//standard building of a NSURLSessionDataTask
let task = myURLSession.dataTaskWithURL(url) { data, response, error in
guard let error == nil else {
//inform the observer there was an error
observer.onError(error)
return
}
let results: [Result] = //make results from the data
//inform the observer there were results
observer.onNext(results)
//inform the observer this observable will not send any more results
observer.onComplete()
}
//start the data task
task.resume()
//!!!! we return a Disposable instance that executes the given block when the observer is disposed of
//this is what gives us the "cancel inflight request" behavior
return AnoymousDisposable {
task.cancel()
}
}
}
}
/**
Some UI View Controller that has a UITextField on it
*/
class ViewController: UIViewController: UITableViewDataSource, UITableViewDelegate {
weak var tableView: UITableView!
weak var textField: UITextField!
weak var button: UIButton!
//in your post, you described dispose bags as a "parallel memory mangement system"
//i liken them to more of an "resource clean up system"
//much like closing a file handle, or unsubscribing form NSNotifications
let disposeBag = DisposeBag()
//an array of strings we display in our table view
var results = [String]()
override func viewDidLoad() {
// Get the text field, throttled, filtered, distinct
let o: Observable<String> =
textField.rx_text
//only send me the text field's value every 3/10th of a second
.throttle(0.3)
//only send me the text field's value when it is different than before
.distinctUntilChanged()
//filter out queries less than 3 characters
.filter { query in return query.characters.count > 3 }
// Get button taps *with the latest value of the text field*
let p: Observable<String> = button.rx_tap.withLatestFrom(o)
// Merge the two and then carry on like our prior example.
// Merge requires observables with the same elemnt type.
//
// In short, the last value from o gets put into the
// observable stream for p; if *either* happens we should
// signal the observable.
let q: Observable<String> = Observable.of(o, p).merge()
//map expects that you return a type that can be converted to an observable
//such as an array which you see below in the other map
//since we are returning a function that returns an observable
//we need to flatten it so that we don't end up with a returned type of Observable<Observable<[Result]>>
//the "latest" uses the latest value emitted by the text field and disposes the other in-flight observables (network requests)
q.flatMapLatest { query in
Fetcher.performQuery(query)
//convert the Result struct into an array of strings
}.map { results in
// Extract the text out of this object; standard Swift.
results.map { result in return result.text }
}
//at this point we have an Observable<[String]> `o` that we can "subscribe" to
//subscribing to o will emit a new array of strings for every completed network request,
//which we use as the data in a table view
//there are better ways to display data in a tableview using rxcocoa, but for this simple example it should suffice
//any errors that may have happened will bubble up to our observable (ie, the network request failed),
//which we can subscribe to as well with a subscribeError(onError:((ErrorType) -> Void)). this has been omitted for clarity
o.subscribeNext { [weak self] results in
self?.results = results
self?.tableView.reloadData()
}
//table view code omitted for clarity
}
@jemmons
Copy link

jemmons commented Apr 8, 2016

If you changed the file's extension to .swift, this would magically get a bunch easier to read.

@victorpimentel
Copy link

For future readers:

//a somewhat contrived example of how reactive programming can be useful
//some real concerns that are omitted, but can be easily accomplished using rxswift are
//error handling, displaying loading indicators, etc


// A result object that comes from the network.
// The contents are irrelevant for this example.
struct Result {
  let text: String
  let someOtherThing: String
}

// An object that can get data from the network 
struct Fetcher {
  /**
  Performs a network query, and returns 0 or more results as an array.
  - Parameter query: The query to complete. Comes from the UI
  - Returns: An observable with 0 or more results
  */
  static func performQuery(query: String) -> Observable<[Result]> {
    // Go to the network
    // Get data
    // Transform into Result objects
    // If the returned Observable is disposed, it will cancel the network request.
    Observable.create { observer in
      //standard building of a NSURLSessionDataTask
      let task = myURLSession.dataTaskWithURL(url) { data, response, error in
        guard let error == nil else { 
          //inform the observer there was an error
          observer.onError(error) 
          return 
        }
        let results: [Result] = //make results from the data
        //inform the observer there were results
        observer.onNext(results)
        //inform the observer this observable will not send any more results
        observer.onComplete()

      }
      //start the data task
      task.resume()

      //!!!! we return a Disposable instance that executes the given block when the observer is disposed of
      //this is what gives us the "cancel inflight request" behavior
      return AnoymousDisposable {
        task.cancel()
      }
    }

  }
}

/**
Some UI View Controller that has a UITextField on it
*/
class ViewController: UIViewController: UITableViewDataSource, UITableViewDelegate {
  weak var tableView: UITableView!
  weak var textField: UITextField!
  weak var button: UIButton!

  //in your post, you described dispose bags as a "parallel memory mangement system" 
  //i liken them to more of an "resource clean up system"
  //much like closing a file handle, or unsubscribing form NSNotifications
  let disposeBag = DisposeBag()
  //an array of strings we display in our table view
  var  results = [String]()

  override func viewDidLoad() {

    // Get the text field, throttled, filtered, distinct
    let o: Observable<String> = 
      textField.rx_text
        //only send me the text field's value every 3/10th of a second
        .throttle(0.3)
        //only send me the text field's value when it is different than before
        .distinctUntilChanged()
        //filter out queries less than 3 characters
        .filter { query in return query.characters.count > 3 }

    // Get button taps *with the latest value of the text field*
    let p: Observable<String> = button.rx_tap.withLatestFrom(o)

    // Merge the two and then carry on like our prior example.
    // Merge requires observables with the same elemnt type.
    //
    // In short, the last value from o gets put into the
    // observable stream for p; if *either* happens we should
    // signal the observable.
    let q: Observable<String> = Observable.of(o, p).merge()

    //map expects that you return a type that can be converted to an observable
    //such as an array which you see below in the other map
    //since we are returning a function that returns an observable
    //we need to flatten it so that we don't end up with a returned type of Observable<Observable<[Result]>>
    //the "latest" uses the latest value emitted by the text field and disposes the other in-flight observables (network requests)
    q.flatMapLatest { query in
      Fetcher.performQuery(query)
    //convert the Result struct into an array of strings
    }.map { results in
      // Extract the text out of this object; standard Swift.
      results.map { result in return result.text } 
    }

    //at this point we have an Observable<[String]> `o` that we can "subscribe" to
    //subscribing to o will emit a new array of strings for every completed network request, 
    //which we use as the data in a table view

    //there are better ways to display data in a tableview using rxcocoa, but for this simple example it should suffice
    //any errors that may have happened will bubble up to our observable (ie, the network request failed),
    //which we can subscribe to as well with a subscribeError(onError:((ErrorType) -> Void)). this has been omitted for clarity

    o.subscribeNext { [weak self] results in 
      self?.results = results
      self?.tableView.reloadData()
    }

    //table view code omitted for clarity
}

@zilberas
Copy link

I'm very new to RxSwift, but I think there's a typo in a o.subscribeNext call. Shouldn't it be q.subscribeNext? If not - I tottaly don't get this RX thing..

@DivineDominion
Copy link

@zilberas right! The result of q's transformation is unused; whatever that is should be subscribed to.

Removing the "o" from "o.subscribeNext" will turn this into a continuation of the chain of transformations (with lots of whitespace and comments in between), which should work, too. (Provided your take care of the Disposable, i.e. the strong reference to the subscription.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment