In the previous post, I have only mentioned different, sort of "predefined" Observable operators. This time, I'd like to show how you can create your own Observables and define all the events that you would like to be emitted to your subscribers.
Example:
func creatingObservables() -> Observable<String> {
Observable<String>.create { (observer) -> Disposable in
observer.onNext("hello")
observer.onNext("world")
observer.onCompleted()
return Disposables.create()
}
}
And if we have a subscriber to the observable above, it will look like this:
self.creatingObservables()
.subscribe { (event) in
print("event: ", event)
}
.disposed(by: bag)
And, the console will print the following:
event: next(hello)
event: next(world)
Or, you can do this, too:
self.creatingObservables()
.subscribe { event in
print("event: ", event)
} onError: { error in
debugPrint("error: ", error.localizedDescription)
} onCompleted: {
debugPrint("completed")
} onDisposed: {
debugPrint("disposed")
}
.disposed(by: bag)
And, the console will print the following:
event: hello
event: world
"completed"
"disposed"
Let's update the first Observable function that we created:
private func creatingObservables() -> Observable<String> {
Observable<String>.create { (observer) -> Disposable in
observer.onNext("hello")
observer.onCompleted()
observer.onNext("world")
return Disposables.create()
}
}
What do you think will happen?
Since we already sent out a "completed" event, the "world" will no longer be emitted to the subscribers. On your console, you will only see the "hello" string being printed out.
event: hello
"completed"
"disposed"
Same goes when you send an error:
private func creatingObservablesWithError() -> Observable<String> {
Observable<String>.create { (observer) -> Disposable in
observer.onNext("hello")
observer.onError(MyError.anError)
observer.onNext("world")
return Disposables.create()
}
}
Subscribe to the Observable above with error:
self.creatingObservablesWithError()
.subscribe { (event) in
print("event: ", event)
}.disposed(by: bag)
Since we sent out an error, the "world" will no longer be emitted to the subscribers, as well. And on your console, you will see something like this:
event: next(hello)
event: error(anError)
No onCompleted or onError event
When you don't add any of these events, you have just created a memory leak. Because the observable will never complete, and the disposable will never be disposed.
Traits
There are 3 kinds of traits in RxSwift: Single, Completable and Maybe. Please note that how I use these traits are my personal preferences and experience, and may/may not necessarily be for these purposes only.
Single
Only emits onNext or onError events.
Useful for one-time processes that will either succeed of fail. I usually use Singles when doing API requests.
Completable
Only emits onCompleted or onError events
No onNext (values)
I sometimes use this for API requests that only returns a boolean value if it has succeeded, or not.
Can also be used when you need user permission, like PushNotifications, or Location, or LocalAuthentication, or even custom alerts that only accepts Yes / No.
Maybe
Can either emit onNext, or an onCompleted or onError events
Can be used in an operation that could succeed or fail, and can also send values on success, or not.
Examples:
Single - I wanted to concatenate strings as long as they are not empty strings.
func testConcatenateStrings(first: String, second: String) -> Single<String> {
return Single<String>.create { single in
if !first.isEmpty && !second.isEmpty {
single(.success("\(first)\(second)"))
} else {
single(.error(MyError.emptyString))
}
return Disposables.create()
}
}
Subscriber:
testConcatenateStrings(first: "Jen", second: "nifer")
.subscribe { mergedText in
debugPrint(mergedText)
} onError: { error in
debugPrint("error: ", (error as! MyError).localizedDesciption)
}
.disposed(by: bag)
Console:
"Jennifer"
Now, if one of the strings is empty, the console will print:
"error: " "Empty String"
Single API Call Sample:
func testAPICall(userId: String) -> Single<UserInfo> {
return Single<UserInfo>.create { [weak self] single in
let request = UserInfoRequest()
request.id = userId
if let future = self?.client.getUserDetails(request).response {
future.whenSuccess { (response) in
single(.success(UserInfo(firstname: response.firstname,
lastName: response.lastname)))
}
future.whenFailure { (error) in
single(.error(error))
}
}
return Disposables.create()
}
}
Completable - for user permission requests, local authentication sample.
func authenticate() -> Completable {
return Completable.create { (completable) -> Disposable in
let localAuthenticationContext = LAContext()
localAuthenticationContext.localizedCancelTitle = "Cancel"
var authorizationError: NSError?
let reason = "The Reason"
if localAuthenticationContext.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: &authorizationError) {
localAuthenticationContext.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: reason) { (success, evaluationError) in
if success {
completable(.completed)
} else {
completable(.error(LocalAuthenticationError.failedToAuthenticate))
}
}
} else {
// No Password/Biometrics setup in Device
completable(.completed)
}
return Disposables.create()
}
}
Maybe - for the sake of making an example, it will either return a success(value), or a completed or an error.
func testMaybeOddEven(number: Int) -> Maybe<String> {
return Maybe<String>.create { maybe in
if number > 0 {
if number % 2 == 0 {
maybe(.success("Even number: \(number)"))
} else {
maybe(.success("Odd number: \(number)"))
}
} else if number == 0 {
maybe(.completed)
} else {
maybe(.error(MyError.invalidNumber))
}
return Disposables.create()
}
}
Subscriber:
testMaybeOddEven(number: 0)
.subscribe { result in
debugPrint(result)
} onError: { error in
debugPrint((error as! MyError).localizedDesciption)
} onCompleted: {
debugPrint("completed")
}
.disposed(by: bag)
Comments