I used to use ReactiveCocoa in Objective-C but I've since switched to RxSwift as I found it easier to understand than RAC4. However there's something I used to do in RAC that was useful:
#weakify(self);
[[RACCommand alloc] initWithEnabled:RACObserve(self, valid) signalBlock:^RACSignal *(id input) {
#strongify(self);
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//make network call
//send responseObject to subscriber
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
return nil;
}] materialize];
}];
This allowed me to subscribe to the command for it's executing state as well as its execution signals so that I could observe data that is returned from the call.
I'm not sure how to reproduce this with RxSwift Action. I am only able to subscribe to its executing observable:
var loader: NotificationType?
formButton.rx_action!.executing.subscribeNext({ [weak self] (executing) -> Void in
if executing {
loader = self?.showNotification(.Loading, title: self?.viewModel.loaderTitle.value, message: "Please wait".localized, timeout: -1)
}
else {
if let loader = loader {
loader.dismiss()
}
}
}).addDisposableTo(disposeBag)
But I then have to create an additional PublishSubject to send my response data:
viewModel.submitSubject.subscribe(onNext: { (response) -> Void in
print(response)
}, onError: { (error) -> Void in
print(error)
}, onCompleted: { () -> Void in
//completed
}) { () -> Void in
}.addDisposableTo(disposeBag)
Is there a way to create a similar pattern in RxSwift with Action?
This is possible with Action but currently it's not straightforward. The problem is that to set an Action property on an object, that object's property must declare the full generic type of Action, which is typically Action<Void, Void> (this is typealias'd to CocoaAction). The two generic types are the input and output, respectively. We chose Void because it represents the fact that work has been done, but doesn't care about what work it is. It's not a perfect solution, since it leads to the problem you're currently facing, and I'm sorry about that.
You want to subscribe to the output of the action's signal, but due to using Void as the output, you can't. The PublishSubject approach you have here is one workaround, another solution would be to use the Void as an output type; you could use errors to indicate failure and Void() to indicate success, but all the work you want to do would need to be encapsulated in the Action's signal. I'd probably use the second approach, but it might not work in all cases.
We have an issue to deal with this but I haven't had time to give it much thought. Any suggestions or resources you have would be awesome 🙇
Related
just trying to implement SwiftUI and Combine in my new project.
But stuck in this:
func task() -> AnyPublisher<Int, Error> {
return AnyPublisher { subscriber in
subscriber.receive(Int(arc4random()))
subscriber.receive(completion: .finished)
}
}
This produces the following compiler error:
Type '(_) -> ()' does not conform to protocol 'Publisher'
Why?
Update
Actually Random here is just as an example. The real code will look like this:
func task() -> AnyPublisher<SomeCodableModel, Error> {
return AnyPublisher { subscriber in
BackendCall.MakeApiCallWithCompletionHandler { response, error in
if let error == error {
subscriber.receive(.failure(error))
} else {
subscriber.receive(.success(response.data.filter))
subscriber.receive(.finished)
}
}
}
}
Unfortunately, I don't have access to BackendCall API since it is private.
It's kind of pseudocode but, it pretty close to the real one.
You cannot initialise an AnyPublisher with a closure accepting a Subscriber. You can only initialise an AnyPublisher from a Publisher. If you want to create a custom Publisher that emits a single random Int as soon as it receives a subscriber and then completes, you can create a custom type conforming to Publisher and in the required method, receive(subscriber:), do exactly what you were doing in your closure.
struct RandomNumberPublisher: Publisher {
typealias Output = Int
typealias Failure = Never
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(Int.random(in: 0...Int.max))
subscriber.receive(completion: .finished)
}
}
Then in your task method, you simply need to create a RandomNumberPublisher and then type erase it.
func task() -> AnyPublisher<Int, Never> {
return RandomNumberPublisher().eraseToAnyPublisher()
}
If all you want is a single random value, use Just
fun task() -> AnyPublisher<Int, Never> {
return Just(Int.random(in: 0...Int.max)).eraseToAnyPublisher()
}
Sidenote: don't use Int(arc4random()) anymore.
You're likely better off wrapping this in a Future publisher, possibly also wrapped with Deferred if you want it to response when subscriptions come in. Future is an excellent way to wrap external async API calls, especially ones that you can't fully control or otherwise easily adapt.
There's an example in Using Combine for "wrapping an async call with a Future to create a one-shot publisher" that looks like it might map quite closely to what you're trying to do.
If you want it to return more than a single value, then you may want to compose something out of PassthoughSubject or CurrentValueSubject that gives you an interface of -> AnyPublisher<YourType, Error> (or whatever you're looking for).
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
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 Completable being returned from a simple function.
This is not an async call, so I just need to return a succcessful completion or error depending on a conditional (using Rx here so I can tie into other Rx usages):
func exampleFunc() -> Completable {
if successful {
return Completable.just() // What to do here???
} else {
return Completable.error(SomeErrorType.someError)
}
}
The error case works pretty easily, but am having a block on how to just return a successful completable (without needing to .create() it).
I was thinking I just need to use Completable's .just() or .never(), but just is requiring a parameter, and never doesn't seem to trigger the completion event.
.empty() is the operator I was looking for!
Turns out, I had mixed up the implementations of .never() and .empty() in my head!
.never() emits no items and does NOT terminate
.empty() emits no items but does terminates normally
So, the example code above works like this:
func exampleFunc() -> Completable {
if successful {
return Completable.empty()
} else {
return Completable.error(SomeErrorType.someError)
}
}
Here is the documentation on empty/throw/never operators.
I would be more inclined to do the following:
func example() throws {
// do something
if !successful {
throw SomeErrorType.someError
}
}
Then in order to tie it into other Rx code, I would just use map as in:
myObservable.map { try example() }
But then, mapping over a Completable doesn't work because map's closure only gets called on next events. :-(
I tend to avoid Completable for this very reason, it doesn't seem to play well with other observables. I prefer to use Observable<Void> and send an empty event before the completed...
Something like this:
let chain = Observable<Void>.just()
let foo = chain.map { try example() }
foo.subscribe { event in print(event) }
I have a login button with a RACCommand assigned to it to perform a login request and return me a user object.
I want to handle the errors from the buttons signal errors.
loginButton.rac_command.executionSignals.flatten()
.logAll()
.subscribeNext { (_:AnyObject!) -> Void in
println("Sent next")
}
so i added the doError block before the subscribeNext block but now the code doesn't compile:
loginButton.rac_command.executionSignals.flatten()
.logAll()
.doError { (err:NSError) -> Void in
println(err.description)
}
.subscribeNext { (_:AnyObject!) -> Void in
println("Sent next")
}
Now it comes back with the error:
cannot invoke 'subscribeNext' with an argument list of type '(AnyObject! -> Void)'
Since the signature of doError is :
- (RACSignal *)doError:(void (^)(NSError *error))block;
I'm assuming that its giving me back the same signal that was passed to the doError block so i can subscribeNext to it.
Not sure why this fails to compile at all though.
Any insight to this issue is much appreciated.
Typo is in argument of doError() (and incorrectly identified by compiler): it should be of type (NSError!) -> Void instead of (NSError) -> Void. Modified like this it compiles fine:
loginButton.rac_command.executionSignals.flatten()
.logAll()
.doError { (err:NSError!) -> Void in
println(err.description)
}
.subscribeNext { (_:AnyObject!) -> Void in
println("Sent next")
}
Additional remarks
Errors from RACCommand are caught internally and sent on errors()
signal, so none are sent on executionSignals() signal.
doNext(), doError(), doComplete() methods are to be used for
injecting side effects to the signal. So you typically use them while
creating signal, not when handling its events. You might want to use
subscribeNext(nextBlock:error:completed:) method (or one of its convenience forms) instead.