I am adopting the MVVM pattern in my iOS application. I expose a range of Observables as public properties in my view model and bind the UI to these properties. These Observables are created from a private connectable observable.
A view controller class then calls the "execute" method to fire the network request. However, if it fails for any reason, I'd like to be able to call "execute" again but this does not work. I believe this is due to the fact that the connectable observable has completed.
How can I achieve this without having to recreate the view model each time? I know I could do this by transforming a simple execute publish subject to the userDetailsObservable by using flatMap but I rely on the onCompleted event for other functionality. The onCompleted event would be lost as the publish subject remains active.
Connectable Observable Solution
class ViewModel {
public var userName: Observable<String> {
self.userDetailsObservable.map {
return $0["username"]
}
}
public var address: Observable<String> {
self.userDetailsObservable.map {
return $0["address"]
}
}
public func execute() {
self.userDetailsObservable.connect()
}
private lazy var userDetailsObservable: ConnectableObservable<JSON> {
return Observable.create { observer in
// execute network request
// assume there is a json object and error object returned
if error != nil {
observer.onError(error)
} else {
observer.onNext(json)
}
observer.onCompleted()
}.publish()
}
}
The FlatMap solution
This would execute the network request every time an event is pushed on the execute subject. (execute.onNext()). The problem here is that the onCompleted event is lost as we are transforming a publish subject.
class ViewModel {
public var userName: Observable<String> {
self.userDetailsObservable.map {
return $0["username"]
}
}
public var address: Observable<String> {
self.userDetailsObservable.map {
return $0["address"]
}
}
public var execute: PublishSubject<Void>()
private lazy var userDetailsObservable: Observable<JSON> {
return self.execute.flatMapLatest { _ in
Observable.create { observer in
// execute network request
// assume there is a json object and error object returned
if error != nil {
observer.onError(error)
} else {
observer.onNext(json)
}
observer.onCompleted()
}
}.share()
}
You should use catchError and return a default value ("" for instance).
It’s required to prevent the observable from being disposed when you receive an error from the API.
Related
I have an async function that currently looks something like this
func startLoginFlow() {
IdentityProvider.shared.login { success, error in
// on success a user has completed authentication
if success {
delegate?.userIsAuthenticated()
}
// on error something wen't wrong
....
}
}
Essentially on success a delegate method is called and some action takes place as a result.
I'd like to wrap this as an observable instead. I do not have the option refactoring IdentityProvider.shared.login.
I essentially just need the observable to emit so I can subscribe and take action elsewhere using onNext.
I am currently doing the following
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(Void())
}
}
return Disposables.create()
}
}
Is this the best way to do this? I wasn't sure if I should use Observable.of and subscribe to the result of IdentityProvider.shared.login
This is how I create Observables as well. The only thing I would note is to add in the errors so you can handle your observables when it errors out, and the completion, as well, to signal that your observable is complete.
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(())
observable.onCompleted()
} else {
observable.onError(error)
}
}
return Disposables.create()
}
}
Observable.of's work in this case as well. It just emits the completed method. You can test this out yourself, if you were trying to create an Observable<String>, with both methods.
I find that doing Observable.create is beneficial here as you're doing network requests and that you can control how you want your observables to error, fail, or be completed.
Someone here gave a pretty good example as well:
Rxswift What difference between Observable.of and Observable<String>.create
CONTEXT
I would like to run 3 different operations sequentially using RxSwift:
Fetch products
When products fetching is done, delete cache
When cache delete is done, save new cache with products from step 1
These are the function definitions in my services:
struct MyService {
static func fetchProducts() -> Observable<[Product]> {...}
static func deleteCache() -> Observable<Void> {...}
static func saveCache(_ products: [Product]) -> Observable<Void> {...}
}
I implement that behavior usually with flatMapLatest.
However, I will lose the result of the 1st observable ([Product]) with that approach, because the operation in the middle (deleteCache) doesn't receive arguments and returns Void when completed.
struct CacheViewModel {
static func refreshCache() -> Observable<Void> {
return MyService.fetchProducts()
.flatMapLatest { lostProducts in MyService.deleteCache() }
.flatMapLatest { MyService.saveCache($0) } // Compile error*
}
// * Cannot convert value of type 'Void' to expected argument type '[Product]'
}
The compile error is absolutely fair, since the operation in the middle 'breaks' the passing chain for the first result.
QUESTION
What mechanism is out there to achieve this serial execution with RxSwift, accumulating results of previous operations?
service
.fetchProducts()
.flatMap { products in
return service
.deleteCache()
.flatMap {
return service
.saveCache(products)
}
}
The easiest solution would be, just to return a new Observable of type Observable<Products> using the static method in the Rx framework just within the second flatMap(), passing in the lostProducts you captured in the flatmap-closure, i.e.:
static func refreshCache() -> Observable<Void> {
return MyService.fetchProducts()
.flatMapLatest { lostProducts -> Observable<[Product]> in
MyService.deleteCache()
return Observable.just(lostProducts)
}
.flatMapLatest { MyService.saveCache($0) } // No compile error
}
That way you are not losing the result of the first call in the flatMap, but just pass it through after having cleared the cache.
you can use do(onNext:) for deleting the cache data and then in flatMapLatest you can save the products. Optionally SaveCache and DeleteCache should return Completable so that you can handle error if the save or delete operation failed.
struct CacheViewModel {
static func refreshCache() -> Observable<Void> {
return MyService.fetchProducts()
.do(onNext: { _ in
MyService.deleteCache()
}).flatMap { products in
MyService.saveCache(products)
}
}
}
I would like to learn about promises in swift-4. How to use multiple then statements and done, catch blocks.
Here I am trying to get the value from the promise. But I'm getting errors. Could someone help me to understand promises?
Here is my code.
import UIKit
import PromiseKit
struct User {
var firstname : String?
var lastname : String?
}
struct APIError {
var message : String?
}
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let userPromise : Promise = self.getUserDetails()
userPromise.then { user -> Void in
//print(user.f)
}
}
func getUserDetails()->Promise<User> {
return Promise<User> { resolve in
let user = User(firstname: "Scot", lastname: "dem")
if ((user.firstname?.count) != nil) {
resolve.fulfill(user)
} else {
let error = APIError(message: "User not valid")
resolve.reject(error as! Error)
}
}
}
}
Once I get the user details I want to make a full name, uppercase promises which are dependent on userPromise.
I would like to use multiple then, done, finally blocks. Just want to understand usage.
Why I'm getting an error here when we use userPromise.then { user -> Void in
what should I give inside the block
In PromiseKit 6, then can no longer return Void. This is mainly due to the tuplegate issue in Swift 4.
Quote from PromieKit 6 Release News
With PromiseKit our then did multiple things, and we relied on Swift
to infer the correct then from context. However with multiple line
thens it would fail to do this, and instead of telling you that the
situation was ambiguous it would invent some other error. Often the
dreaded cannot convert T to AnyPromise. We have a troubleshooting
guide to combat this but I believe in tools that just work, and when
you spend 4 years waiting for Swift to fix the issue and Swift doesn’t
fix the issue, what do you do? We chose to find a solution at the
higher level.
So we split then into then, done and map.
then is fed the previous promise value and requires you return a promise.
done is fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
Hence .then { (user) -> Void in is no longer valid and that's why you're getting an error.
It's now designed to return a Thenable like so:
userPromise.then { user -> Promise<User> in
//return yet another promise
}
The .then that used to return Void is now it's own .done.
i.e:
userPromise.done { (user) in
print(user)
}
So when you mix 'em up:
We get:
userPromise
.then { (user) -> Promise<User> in
//return another Promise
return self.uppercasePromise(on: user)
}
.done { (user) in
/*
Depending on your sequence, no more promises are left
and you should have a matured user object by now
*/
print(user)
}
.catch { (error) in
print(error)
}
.finally {
print("finally")
}
func uppercasePromise(on user: User) -> Promise<User> {
/*
Didn't understand your statement but do whatever you meant when you said:
"Once I get the user details I want to make a full name, "
uppercase promises which are dependent on userPromise."
*/
return Promise { seal in
seal.fulfill(user)
}
}
I have a method which does processing for the events I receive from the server. The method can be called from multiple places in different classes. I want to synchronize the processing of the events using DispatchQueue/Serial Queue to discard the duplicate events in multiple calls. I know about dispatch queues and how it works but I am unable to find the best solution for my problem.
To achieve: By synchronizing I want to ensure sequential processing, to discard duplicate events.
func process(events:[Events]) {
// by synchronizing I want to ensure sequential processing, to discard duplicate events
for event in events {
// process, save to db,
}
// issue notifications, etc
}
class A {
process(events)
}
class B {
process(events)
}
Any help is appreciated. Thanks!
Try something like this:
class Event {
let id: String = ""
}
class EventManager {
static let shared = EventManager()
private let processQueue = DispatchQueue(label: "processQueue")
private var processedEvents = [Event]()
private init() {}
func process(events:[Event]) {
processQueue.async { [unowned self] in
for event in events {
if !self.processedEvents.contains(where: { $0.id == event.id }) {
// process, save to db,
self.processedEvents.append(event)
}
}
// issue notifications, etc
}
}
}
I am new in swift moved from java. And some implementaion of dessign patterns confuse me.
For example I have presudo pattern observer (callback) in java code (there is example below). Namely UI passed own listener to Manager class and listen callbacks isConnected and isDisconnected. If a callback is executed UI class shows certain message "isConnected" or "isDisconnected"
public class UI{
private Manager mManager;
void createManager(){
mManager = new Manager(mManagerLister);
}
public void showMessage(String aMsg){
print(aMsg)
}
private final IManagerListener mManagerLister = new IManagerListener{
void isConnected(){
this.showMessage("isConnected")
}
void isDisconnected(){
this.showMessage("isConnected")
}
}
}
public class Manager{
interface IManagerListener{
void isConnected();
void isDisconnected();
}
private final mListener;
public Manager(IManagerListener aListener){
mListener = aListener;
}
}
How to correctly port this java code to swift code? I tries to port but error message Value of type 'UI' has no member 'showMessage' is shown
public class UI{
var manager: Manager?
var managerListener: IManagerListener?
func createManager(){
managerListener = ManagerListenerImp(self)
manager = Manager(managerListener)
}
public func showMessage(msg: String){
print(msg)
}
class ManagerListenerImp: IManagerListener{
weak var parent: UI
init(parent : UI ){
self.parent = parent
}
func isConnected(){
parent.showMessage("isConnected")
// Value of type 'UI' has no member 'showMessage'
}
..........
}
}
Perhaps exists more gracefully a way to use callbacks and my way is not correctly?
There are multiple ways to achieve it.
Delegate Pattern (Using Protocols which are nothing but interfaces
in Java)
Using Blocks/Closures
Using KVO
Because you have used interfaces am elaborating on Delegate pattern below.
Modify your code as below
Declare a protocol
#objc protocol ManagerListenerImp {
func isConnected()
}
Declare a variable in Manager class
class Manager {
weak var delegate : ManagerListenerImp? = nil
}
Confirm to ManagerListenerImp in your UI class
extension UI : ManagerListenerImp {
func isConnected () {
//your isConnected implementation here
}
}
Pass UI instance (self in swift and this in JAVA to manager class)
func createManager(){
manager = Manager()
manager?.delegate = self
}
Finally, whenever you wanna trigger isConnected from Manager class simply say
self.delegate?.isConnected()
in your Manager class
Hope it helps
I'm a bit confused by which class has a reference to which, but that shouldn't be too hard to change in the following example.
You might be looking for an Observer Pattern. This can have multiple objects listening to the same changes:
1. ManagerStateListener Protocol
Protocol to be implemented by any class that should react to changes to the state of the Manager
protocol ManagerStateListener: AnyObject {
func stateChanged(to state: Manager.State)
}
2. Manager Class
The Manager class contains:
Its state
A list with listeners
Methods for adding, removing and invoking the listeners
An example class that implements the ManagerStateListener protocol
class Manager {
/// The possible states of the Manager
enum State {
case one
case two
case three
}
/// The variable that stores the current state of the manager
private var _currentState: State = .one
var currentState: State {
get {
return _currentState
}
set {
_currentState = newValue
/// Calls the function that will alert all listeners
/// that the state has changed
invoke()
}
}
/// The list with all listeners
var listeners: [ManagerStateListener] = []
/// A specific listener that gets initialised here
let someListener = SomeListener()
init() {
addListener(someListener) /// Add the listener to the list
}
/// Method that invokes the stateChanged method on all listeners
func invoke() {
for listener in listeners {
listener.stateChanged(to: currentState)
}
}
/// Method for adding a listener to the list of listeners
func addListener(_ listener: ManagerStateListener) {
listeners.append(listener)
}
/// Method for removing a specific listener from the list of listeners
func removeListener(_ listener: ManagerStateListener) {
if let index = listeners.firstIndex(where: { $0 === listener }) {
listeners.remove(at: index)
}
}
}
3. SomeListener Class
An example listener that implements the ManagerStateListener protocol, held by the Manager class
class SomeListener : ManagerStateListener {
func stateChanged(to state: Manager.State) {
/// Do something based on the newly received state
switch state {
case .one:
print("State changed to one")
case .two:
print("State changed to two")
case .three:
print("State changed to three")
}
}
}
I hope this is of any help.