Forked from jamiepinkham/gist:d35904a1eb4d7d0c5d0f6304c003ac33
Last active
December 1, 2016 07:09
-
-
Save cliss/51cb740b14f3cd56ba1d11f2a9a6ba02 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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 | |
} |
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
}
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..
@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
If you changed the file's extension to
.swift
, this would magically get a bunch easier to read.