Let's Explore Reactive Programming. ( RxSwift and RxCocoa )
In Computing , reactive programming is a programming paradigm oriented around data flow and the propagation of changes.
RxSwift provides the fundamentals of Observables and RxCocoa provides extensions to the Cocoa and Cocoa Touch frameworks to take advantage of RxSwift.
Everything in RxSwift is observable sequence and something that subscribes the events emitted by observable sequence.
Array, Dictionary, string will be converted into observable sequence in RxSwift. You can create Observable sequence of any object that conforms “Sequence” protocol.
let’s create some observable sequence :
let helloSwift = Observable.just("Hello World")
let fiboniccaSequence = Observable.from([0,1,1,2,3,5,8])
let dictSequence = Observable.from(["1" : 1, "2" : 2])
let nameSequence = Observable.from(["ashis", "kunal", "ratan", "sam"])
let disposeBag = DisposeBag()
You can subscribe those observable sequence to observe it.
private func handleObservableSequence() {
// subscribe
// event
let eventSubscription = helloSwift.subscribe { (event) in
print(event)
}
eventSubscription.disposed(by: disposeBag)
// onNext
let onNextSubscription = fiboniccaSequence.subscribe(onNext: { (eachElement) in
print(eachElement)
}, onError: nil, onCompleted: nil, onDisposed: nil)
onNextSubscription.disposed(by: disposeBag)
//event
let nameSubsciption = nameSequence.subscribe { (event) in
switch event {
case .next(let val): print(val)
case .completed: print("Completed")
case .error(let error): print(error)
}
}
nameSubsciption.disposed(by: disposeBag)
}
next(Hello World)
completed
0
1
1
2
3
5
8
ashis
kunal
ratan
sam
Completed
Observable Sequence can emit 0 or more signal/event in their lifetime.
(A) .next(value : T): When a value is added to an Observable Sequence, it triggers the next event to its subscribers.
(B) .error(error : Error): If an error happens, the Observable Sequence emits an error event to its subscribers.
(C) .completed : If an Observable Sequence ends normally then it emits a “completed” signal to its subscribers.
If you want to cancel a subscription, you can call dispose method.
eventSubscription.dispose()
Or you can add this to disposeBag which will cancel the subscription automatically in deinit method.
eventSubscription.disposed(by: disposeBag)
A Subject is a special form of Observable sequence, you can subscribe and dynamically add item to it.
There are 4 kinds of subjects are there :
(1) PublishSubject: If you subscribe to it, you will get all the events after subscription.
(2) BehaviourSubject : If you subscribe to it, you will get the most recent element.
(3) ReplaySubject : If you want to replay more than the most recent element to new subscribers then use ReplaySubject.
(4) Variable : Wrapper of BehaviourSubject to look and feel for non-reactive programming style.
The basic difference in terms of number of past N events in 4 subjects :
PublishSubject : 0 catch event
BehaviourSubject and Variable : 1 event (the recent event)
ReplaySubject : N events (based on Buffer capacity )
// let's explore Subjects
var publishSubject = PublishSubject<String>()
var behaviorSubject = BehaviorSubject<String>(value: "Behavior1")
var variable = Variable<String>("Variable1") // wrapper of BehaviorSubject
var replaySubject = ReplaySubject<String>.create(bufferSize: 3) // create a buffer to store the most recently nth number of events are stored
private func handlePublishSubject() {
// send signals
publishSubject.onNext("Event1 - Not printed - PublishSubject")
publishSubject.onNext("Event2 - Not printed - PublishSubject")
// subscribe
publishSubject.subscribe(onNext: { (event) in
print("\n",event)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
publishSubject.onNext("Event3 - Printed - PublishSubject")
publishSubject.onNext("Event4 - Printed - PublishSubject")
}
Event3 - Printed - PublishSubject
Event4 - Printed - PublishSubject
private func handleBehaviorSubject() {
// send signals
behaviorSubject.onNext("Event1 - Not printed - BehaviorSubject")
behaviorSubject.onNext("Event2 - printed - BehaviorSubject") // receive the most recent event
// subscribe
behaviorSubject.subscribe(onNext: { (event) in
print("\n",event)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
behaviorSubject.onNext("Event3 - Printed - BehaviorSubject")
behaviorSubject.onNext("Event4 - Printed - BehaviorSubject")
}
Event2 - printed - BehaviorSubject
Event3 - Printed - BehaviorSubject
Event4 - Printed - BehaviorSubject
private func handleReplaySubject() {
// send signals
replaySubject.onNext("Event1 - Not printed - ReplaySubject") // Buffer capacity is 3
replaySubject.onNext("Event2 - printed - ReplaySubject")
replaySubject.onNext("Event3 - Printed - ReplaySubject")
replaySubject.onNext("Event4 - Printed - ReplaySubject")
// subscribe
replaySubject.subscribe(onNext: { (event) in
print("\n",event)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
replaySubject.onNext("Event5 - Printed - ReplaySubject")
replaySubject.onNext("Event6 - Printed - ReplaySubject")
}
Event2 - printed - ReplaySubject
Event3 - Printed - ReplaySubject
Event4 - Printed - ReplaySubject
Event5 - Printed - ReplaySubject
Event6 - Printed - ReplaySubject
Binding an Observable Sequence to a subject :
private func binding() {
let subject = PublishSubject<String>() // Hot Observable
let observableSequence = Observable<String>.just("Start binding") // Cold Observable
// Subject must subscribe before it sends signal, else signal will not be captured as per Hot Observable property.
subject.subscribe(onNext: { (text) in
print(text)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
// Either
observableSequence.subscribe { (event) in
subject.on(event)
}.disposed(by: disposeBag)
// Or
observableSequence.bind { (event) in
subject.onNext(event)
}.disposed(by: disposeBag)
}
Start binding
Outlets and view model
@IBOutlet private weak var textField: UITextField!
@IBOutlet private weak var buttonOutlet: UIButton!
let vm = ViewModel()
binding textField and button
private func bindingUIElements() {
textField.rx.text.orEmpty.bind { (text) in
print(text)
}.disposed(by: disposeBag)
buttonOutlet.rx.tap.bind { [weak self] in
print("Button tapped")
self?.vm.buttonTappedAction()
}.disposed(by: disposeBag)
}
class ViewModel {
public func buttonTappedAction() {
print("Perform some action")
}
}
Output :
when you write on textField :
H
He
Hel
Hell
Hello
Hello
Hello w
Hello wo
When you tap on button :
Button tapped
Perform some action
It will start executing only when an observer subscribes the observable sequence.
Create a cold observable :
// cold Observable signal
let coldObservable = Observable<String>.create { (observer) -> Disposable in
DispatchQueue.global(qos: .default).async {
Thread.sleep(forTimeInterval: 5.0) // sleep for 5 secs
observer.onNext("Hi I am a cold signal")
observer.onCompleted()
}
return Disposables.create()
}
Subscribed Observer of cold observable :
private func handleColdSignal() {
// observer
coldObservable.subscribe { (event) in
print("\n",event)
}.disposed(by: disposeBag)
}
A Hot observable executes even if it does not have any Observers.
Subjects are examples of Hot observables like PublishSubject etc.
It visualises the transformation of an Observable sequence. It consists of there layer :
< Input , Transformation function , OutPut >
Sometimes if you want to transform (filter, combine, map etc.) the event/signal emitted by Observable sequence and before receiving to the subscribers, you can do that.
print("\nMap\n")
Observable<Int>.of(1,2,3,4).map { element in
return element * 10
}.subscribe(onNext: { (val) in
print(val)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
Map
10
20
30
40
print("\nflatMap\n")
let observableSequence1 = Observable<Int>.of(1,2)
let observableSequence2 = Observable<Int>.of(3,4)
let sequences = Observable.of(observableSequence1,observableSequence2)
sequences.flatMap { return $0 }.subscribe(onNext: { (val) in
print(val)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
flatMap
1
2
3
4
print("\nFiltering\n")
let observable3 = Observable<Int>.of(1,2,3,4,5,6)
observable3.filter { (each) -> Bool in
return each % 2 == 0
}.subscribe(onNext: { (val) in
print(val)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
Filtering
2
4
6
print("\nDistinctUntilChanged\n")
let observable4 = Observable<Int>.of(1,1,1,2,3,3,3,3,3,3,4)
observable4.distinctUntilChanged().subscribe(onNext: { (each) in
print(each)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
DistinctUntilChanged
1
2
3
4
print("\nScan\n")
let observable5 = Observable<Int>.of(1,2,3,4,5)
observable5.scan(0) { (accumulator, val) -> Int in
return accumulator + val
}.subscribe(onNext: { (result) in
print(result)
}, onError: nil, onCompleted: nil, onDisposed: nil).disposed(by: disposeBag)
Scan
1
3
6
10
15
http://swiftpearls.com/RxSwift-for-dummies-1-Observables.html
https://academy.realm.io/posts/slug-max-alexander-functional-reactive-rxswift/
https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa
http://www.tailec.com/blog/understanding-publish-connect-refcount-share
http://lukajcb.github.io/blog/reactivex/2016/05/04/reactive-uis-with-rx-swift.html
https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift