functional reactive programming - rxswift
TRANSCRIPT
Functional Reactive Programming
Focus on mobile development
OutsourcingProduct Development
Functional & Reactive
Functional &Avoid state
Immutable dataDeclarative programming
& Reactive
Data drivenValues over time
Streams
Reactive Cocoa RxSwift
RxSwift
Based on ReactiveX
RxSwift
Based on ReactiveX
Observable pattern
Iterator pattern}Functional programming
} Reactive
Rx.Net
Rx.JS
Rx.Jav
a
Rx.Cpp
Rx.Rub
y
Rx.Sca
la, RxC
losure
Rx.Py, R
xPHP
Rx.Kotl
in
Rx.Swift
Reactive Extensions
nov. 2009mar. 2010
mar. 2012nov. 2012
dec. 2012jan. 2013
mar. 2013out. 2013
feb. 2015
ObservableEmits events over time
ObserverSubscribe to listen events emitted by the observable
ObservableLife cycle observable sequence
marble diagrams
// Terminate normally
// Terminate with error
// Endless sequence1 2 3
tap tap tap tap
a b
ObservableEmits events over time
protocol Observable { associatedtype E func on(_ event: Event<E>) }
enum Event<Element: Any> { case Next(Any) case Error(Error) case Completed }
ObservableEmits events over time
Array<T>
Observable<T>
ObservableCreate Operator
let observable = Observable.just("Hello World")
ObservableListen Operator
let observable = Observable.just("Hello World")
// next(Hello World) // completed
observable.subscribe(onNext: { (value) in print(value) // Pump out an element }, onError: { (error) in // Catch error }, onCompleted: { // Catch completed }, onDisposed: { // Dispose the subscription })
ObservableCreate Operator
let disposeBag = DisposeBag() Observable.from(["🐶", "🐱", "🐭", "🐹"]) .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag)
ObservableCreate Operator
let disposeBag = DisposeBag() Observable.from(["🐶", "🐱", "🐭", "🐹"]) .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag)
🐶 🐱 🐭 🐹
🐶 🐱 🐭 🐹
////////
ObservableTransforming Operators
let disposeBag = DisposeBag() Observable.of(2, 3, 4) .map { $0 * $0 } .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag)
Observable
let disposeBag = DisposeBag() Observable.of(2, 3, 4) .map { $0 * $0 } .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag)
4 16
2 43
9
map { $0 * $0 }
Transforming Operators
CombineLatest
JustFlatMapLatest
Debounce
rxmarble.com
Traditional X Reactive
class ElementViewController: UIViewController { @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var button: UIButton! override func viewDidLoad() { button.isEnabled = false usernameTextField.delegate = self passwordTextField.delegate = self } func enableLoginButton(username: String, password: String) { button.isEnabled = (username != "" && password != "") } }
extension ElementViewController: UITextFieldDelegate { func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { // check textfield } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // check textfield return true }
class ElementViewController: UIViewController { @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var button: UIButton! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() Observable.combineLatest(usernameTextField.rx.text, passwordTextField.rx.text) { username, password in return username != "" && password != "" } .subscribe { button.isEnable = $0 } .addDisposableTo(disposeBag) }
}
class ElementViewController: UIViewController { @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var button: UIButton! let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() Observable.combineLatest(usernameTextField.rx.text, passwordTextField.rx.text) { username, password in return username != "" && password != "" } .subscribe { button.isEnable = true } .addDisposableTo(disposeBag) }
}
class ElementViewController: UIViewController { @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var button: UIButton! override func viewDidLoad() { button.isEnabled = false usernameTextField.delegate = self passwordTextField.delegate = self } func enableLoginButton(username: String, password: String) { button.isEnabled = (username != "" && password != "") } }
extension ElementViewController: UITextFieldDelegate { func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { // check textfield } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { // check textfield return true }
40%Saving
Traditional Reactive
Expressivity
Senquence API Calls
1. Get userID 2. Get credit card account ID 3. Get credit card info
Alamofire.request("https://fake/login", method: .post) .responseJSON { (response) in Alamofire.request("https://fake/creaditToken", method: .post, parameters: ["creditToken": response.id]) .responseJSON { (response) in Alamofire.request("https//fake/creaditCard", method: .post, parameters: ["creditCard": response.token]) .responseJSON { // get credit card info } } }
class CreditCardService { func getCreditCards() { let service = [] as! Service service.rxLogin(username: "[email protected]", password: "12345") .flatMap { (authResponse) -> Observable<CreditCardAccount> in return service.rxCredidCardAccount(userId: authResponse.userId) } .flatMap { (creditCardAccount) -> Observable<[CreditCardInfo]> in return service.rxAllCreditCards(userId: creditCardAccount.cardsId) } .subscribe { (creditCardInfo) in print(creditCardInfo) } } }
Marvelous
static func request(endpoint: Resource) -> Observable<[String:AnyObject]> { return Observable.create { observer in
} return Disposables.create { request.cancel() } } }
Request Observable
let request = Alamofire.request(endpoint.path, method: endpoint.method, parameters: endpoint.parameter) .validate() .responseJSON { (response: DataResponse<Any>) in if let err = response.result.error { observer.onError(err) } else { if let result = response.result.value as? [String:AnyObject] { observer.onNext(result) } observer.onCompleted() }
static func request(endpoint: Resource) -> Observable<[String:AnyObject]> { return Observable.create { observer in
} return Disposables.create { request.cancel() } } }
Request Observable
@IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var tableView: UITableView!
// Trigger when reach the bottom of the tableView let trigger = tableView.rx.contentOffset.flatMap { _ in (self.tableView.contentOffset.y + self.tableView.frame.size.height + 20 > self.tableView.contentSize.height) ? Observable.just() : Observable.empty() }
let searchResult = searchBar.rx.text.asObservable() .debounce(3, scheduler: MainScheduler.instance) .flatMapLatest { query -> Observable<[Character]> in return CharacterAPI().heros(search: query!, trigger: trigger) }.catchErrorJustReturn([Character]())
searchResult.bindTo(tableView.rx.items(cellIdentifier: "HERO_CELL")) { row, character, herocell in let cell: HeroTableViewCell = (herocell as? HeroTableViewCell)! cell.heroNameLabel.text = character.name cell.downloadableImage = UIImage.imageFrom(urlString: character.getHeroImagePath()) }.addDisposableTo(disposeBag)
ViewController
Thanks• reactive.io• github.com/frelei/marvelous• gitHub.com/ReactiveX/RxSwift
• rxmarbles.com
• slack.rxswift.org