I need a function that encapsulates a complicated IAP purchase tree into a simple attemptPurchase function that returns a boolean observable (true -> success, false -> cancelled, error -> any error)
But I am stumped at how to create that function, mainly because the start of the decision is async.
Decision tree and code below.
// fails -> missing return function
// but I cannot return the credit check, since the execution is different depending on the result
func attemptPurchase(amount: Int) -> Observable<Bool>{
let creditCheck = creditCheck(amount)
creditCheck.filter{$0}.subscribeNext{ _ in
return Observable.just(true)
}
creditCheck.filter{$0}.subscribeNext{ _ in
return confirmIAP().processIAP()
}
}
func creditCheck(amount: Int) -> Observable<Bool>{
return API.creditCheck.map{$0 > amount}
}
func confirmIAP() -> Observable<Bool> {
// UI for confirming IAP
}
func processIAP() -> Observable<Bool> {
// UI for uploading IAP on my server
}
This is how you could do it:
func attemptPurchase(amount: Int) -> Observable<Bool> {
return creditCheck(amount)
.flatMapLatest { (enoughCredit: Bool) -> Observable<Bool> in
if enoughCredit {
return Observable.just(true)
} else {
return confirmIAP()
.flatMapLatest { (isConfirmed: Bool) -> Observable<Bool> in
if isConfirmed {
return processIAP()
} else {
return Observable.just(false)
}
}
}
}
}
From iankeen's answer in the RxSwift slack group:
func attemptPurchase(amount: Int) -> Observable<Bool>{
return creditCheck(amount)
.flatMap { enough in
return (enough ? .just(true) : confirmIAP())
}
.flatMap { ready in
guard ready else { /* failure */ }
return processIAP()
}
}
Related
I'm quite new to RxSwift.
Is there any way I can create a function that will return Observable based on the conditions of two functions below
func isValidPassword(_ str: PublishSubject<String>) -> Observable<Bool> {
return str.asObservable().map {
validator.isValidPasswordRegex($0) && $0.count >= 8
}
}
func isNotEmpty(_ str: PublishSubject<String>) -> Observable<Bool> {
return str.asObservable().map {
$0.count != 0
}
}
This code below is just an example of what I'm trying to achieve, hoped you got the idea.
func someFunc(_ str:PublishSubject<String>) -> Observable<String> {
if !isValidPassword(str){
return "Not a valid password" //should return as an observable string
}
if isNotEmpty(str){
return "String is not empty" //should return as an observable string
}
}
I'm not sure about what you want to achieve here but I think you should use filter operator and not map. In this way you don't change the type of the starting observable but you remove elements that don't respect your conditions.
func isValidPassword(_ str: String) -> Bool {
validator.isValidPasswordRegex(str) && str.count >= 8
}
func someFunc(_ str: PublishSubject<String>) -> Observable<String> {
str
.filter(isValidPassword)
.filter { !$0.isEmpty }
}
Let me introduce you to zip... It allows you to combine multiple Observables.
func someFunc(_ str: Observable<String>) -> Observable<String> {
Observable.zip(isValidPassword(str), isNotEmpty(str))
.map { isValidPassword, isNotEmpty -> String in
if !isValidPassword {
return "Not a valid password"
}
if isNotEmpty {
return "String is not empty"
}
return "" // you didn't specify what it should return here...
}
}
Note that I updated the type signatures of your existing functions:
func isValidPassword(_ str: Observable<String>) -> Observable<Bool>
func isNotEmpty(_ str: Observable<String>) -> Observable<Bool>
Passing Subjects around like that is a recipe for disaster.
Subjects provide a convenient way to poke around Rx, however they are not recommended for day to day use.
-- Introduction to Rx
UPDATE
I think the code would be better if you implemented like this though:
func errorMessage(text: Observable<String>) -> Observable<String> {
text.map(errorMessage)
}
func errorMessage(_ str: String) -> String {
if !isValidPassword(str) {
return "Not a valid password"
}
if isNotEmpty(str) {
return "String is not empty"
}
return ""
}
func isValidPassword(_ str: String) -> Bool
func isNotEmpty(_ str: String) -> Bool
It's much easier to test this way.
Context
I want to wrap the Alamofire.upload into an observable and having info regarding the upload progress.
For that I have created a custom UploadElement that is an enum representing either the progress and the value or the result. So far I have:
enum UploadElement<Result> where Result: Codable {
case progress(Double)
case response(Result)
}
private func buildUploadRequest(url: URL, parts: [Data]) -> Observable<UploadRequest> {
let uploadRequest = manager.upload(
multipartFormData: { multipartFormData in /* build multipart */ },
to: url
)
return Observable.just(uploadRequest)
}
func upload<Result: Codable>(url: URL, parts: [Data]) -> Observable<UploadElement<Result>> {
buildUploadRequest(url: url, parts: parts)
.flatMap { request in
Observable<UploadElement<Result>>.create { observer in
request.response { response in
do {
observer.on(.next(.response(/* decode here */)))
observer.on(.completed)
} catch let error {
observer.on(.error(error))
}
}.uploadProgress { progress in
observer.on(.next(.progress(progress.fractionCompleted)))
}
.resume()
return Disposable.create { request.cancel() }
}
}
}
Now I would like to have an extension on an Observable<UploadEment<Result>> to have a nicer way to be notified.
Basically it would be:
service.upload(url: ..., parts: ...)
.progress { progress in /* */ }
.result { result in /* */ }
.subscribe()
.dispose(by: disposeBag)
To do that I tried:
extension ObservableType where Element == UploadElement<Resource> {
func progress(progressCompletion: #escaping (Double) -> Void) -> Self {
return self.do(onNext: { element in
switch element {
case .progress(let progress): progressCompletion(progress)
case .response: return
}
})
}
func result(resultCompletion: #escaping (Result) -> Void) -> Self {
return self.do(onNext: { element in
switch element {
case .response(let result): resultCompletion(result)
case .progress: return
}
})
}
}
I tried multiple variation of that but the errors that I get are:
Cannot find 'Result in scope'
Reference to generic type ... required argument
Is it possible to achieve something like that?
You just need to move the where clause from class scope down to function scope (shown below).
That said, I don't think breaking out of the monad like this in the middle of a stream is "a nicer way to be notified".
Better would be to break your Observable into two streams and subscribe to each of them:
extension ObservableType {
func progress<Resource>() -> Observable<Double> where Element == UploadElement<Resource> {
self.compactMap { element in
switch element {
case let .progress(progress):
return progress
case .response:
return nil
}
}
}
func result<Resource>() -> Observable<Resource> where Element == UploadElement<Resource> {
self.compactMap { element in
switch element {
case .progress:
return nil
case let .response(resource):
return resource
}
}
}
}
With the above you can now do something like this:
let response = service.upload(url: ..., parts: ...)
.share()
response
.progress()
.subscribe(onNext: { progress in /*...*/ })
.disposed(by: disposeBag)
response
.result()
.subscribe(onNext: { result in /*...*/ })
.dispose(by: disposeBag)
Now you don't have any empty subscribes.
I found something that is working:
extension ObservableType {
func progress<O: Codable>(progressCompletion: #escaping (Double) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
return self.do(onNext: { element in
if case .progress(let progress) = element {
progressCompletion(progress)
}
})
}
func response<O: Codable>(responseCompletion: #escaping (O) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
return self.do(onNext: { element in
if case .response(let response) = element {
responseCompletion(response)
}
})
}
}
Now I can use the "planned" api:
service.update(data: /* ... */)
.progress { progress in /* */ }
.response { result in /* */ }
.subscribe(
onError: { error in /* */ }
)
.dispose(by: disposeBag)
However as Daniel mentioned this might not be the "nicer way of being notified".
I'm getting to know Swift + Swift's Combine framework and wanted to check that my attempt at implementing a retryIf(retries:, shouldRetry:) operator makes sense. In particular, I'm curious if all the .eraseToAnyPublishers are expected/idiomatic.
extension Publisher {
func retryIf(retries: Int, shouldRetry: #escaping (Self.Failure) -> Bool) -> AnyPublisher<Self.Output, Self.Failure> {
self.catch { error -> AnyPublisher<Self.Output, Self.Failure> in
guard shouldRetry(error) && retries > 0 else {
return Fail(error: error).eraseToAnyPublisher()
}
return self.retryIf(retries: retries - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
Assuming that all the AnyPublishers are ok, when do you want to make your own Publisher struct? For example, the regular Combine operator retry returns a Retry<Upstream> struct rather than an AnyPublisher, but I imagine you could implement it along the same lines as the code above, something like:
extension Publisher {
func doOver(tries: Int) -> AnyPublisher<Self.Output, Self.Failure> {
self.catch { error -> AnyPublisher<Self.Output, Self.Failure> in
guard tries > 0 else { return Fail(error: error).eraseToAnyPublisher() }
return self.doOver(tries: tries - 1).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
You can eliminate the final eraseToAnyPublisher, and thus the heap allocation it requires, by defining your own Publisher. For example:
extension Publisher {
func retry(_ retries: Int, if shouldRetry: #escaping (Failure) -> Bool) -> MyPublishers.RetryIf<Self> {
return .init(upstream: self, triesLeft: retries, shouldRetry: shouldRetry)
}
}
enum MyPublishers { }
extension MyPublishers {
struct RetryIf<Upstream: Publisher>: Publisher {
typealias Output = Upstream.Output
typealias Failure = Upstream.Failure
init(upstream: Upstream, triesLeft: Int, shouldRetry: #escaping (Failure) -> Bool) {
self.upstream = upstream
self.triesLeft = triesLeft
self.shouldRetry = shouldRetry
}
var upstream: Upstream
var triesLeft: Int
var shouldRetry: (Failure) -> Bool
func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input {
upstream
.catch {
triesLeft > 0 && shouldRetry($0)
? Self(upstream: upstream, triesLeft: triesLeft - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
: Fail(error: $0).eraseToAnyPublisher()
}
.receive(subscriber: subscriber)
}
}
}
If you want to eliminate the two eraseToAnyPublisher calls inside the catch body, you will have to give up using catch. Instead you will have to implement your own Subscription. Implementing Subscription is much more complicated, because it has to be thread-safe. However, those calls inside the catch body can only happen in the case of an upstream failure, and only one of the calls happens per failure. So if upstream failures are rare, it's probably not worth the effort.
I need to create some sort of closure to return back if it's an optional or forced update. I've created some pseudo code:
func verifyAppVersionWithServer(isForceUpdate: bool -> true, isOptionalUpdate: bool -> true) {
//Some check will be performed here then:
if isForceUpdate {
return isForceUpdate -> true
} else {
return isOptionalUpdate -> true
}
}
I'm not sure how to create a closure in Swift which will then return which of the parameters is true.
It is probably nicer to return an enum that indicates the type of update required.
You would then have something like this:
enum UpdateType {
case None
case Optional
case Required
}
func verifyAppVersionWithServer(completion:(UpdateType) -> Void) {
let anyUpdate = true
let forcedUpdate = false
if anyUpdate {
if forcedUpdate {
completion(.Required)
} else {
completion(.Optional)
}
} else {
completion(.None)
}
}
You would call it as:
verifyAppVersionWithServer { (updateType) in
print("Update type is \(updateType)")
}
Obviously the values would be determined by your server response, not fixed values as I have shown.
You can use something like below
func verifyAppVersionWithServer(parm1: String, withParma2: Bool, completionHandeler: (isSucess: Bool, error : NSError) -> Void) {
//Write your logic
//call complition handeler
completionHandeler(isSucess: true, error: error)
}
Hope this will help
I have a protocol for fetching database objects by PrimaryKey
typealias PrimaryKey = String
protocol PrimaryKeyConvertible {
var pkValue : PrimaryKey { get }
static func pkObject(key: PrimaryKey) -> Self?
}
and I want to extend the SignalProducerType to be able to operate on a SignalProducer.Value of that type.
So the Single object extension (single as in not Array) works fine and implemented as following:
extension SignalProducerType
where Value: PrimaryKeyConvertible
{
func fetchOnMainThread() -> SignalProducer<Value?, Error> {
return
self.map{ (obj: Value) -> PrimaryKey in
return obj.pkValue
}
.observeOn(UIScheduler())
.map{ (key: PrimaryKey) -> Value? in
return Value.pkObject(key)
}
}
}
But when I try to implement it on an Array of these elements i hit some compilation challenges:
extension SignalProducerType
{
func fetchOnMainThread<P: PrimaryKeyConvertible where Self.Value == Array<P>>() -> SignalProducer<[P], Error> { //(1)
return self.map({ (value: Self.Value) -> [PrimaryKey] in
return value.map{ $0.pkValue } //(2)
})
}
}
(1) i suspect that the signature is not communicating the idea to the compiler correctly
(2) produces the following error:
Type of expression is ambiguous without more context
the issue i'm trying to solve is how to let the compiler recognize the the SignalProducer is operating on an Array<P> where P is PrimaryKeyConvertible and have the .map operate on it accordingly ...
my current solution for the array issue is to implement using a generic function as listed below:
func fetchOnMainThread<Value: PrimaryKeyConvertible, Error: ErrorType>
(signal: SignalProducer<[Value], Error>) -> SignalProducer<[Value], Error> {
return signal
.map{ (convertibles: [Value]) -> [PrimaryKey] in
return convertibles.map { $0.pkValue }
}
.observeOn(UIScheduler())
.map{ (keys: [PrimaryKey]) -> [Value] in
return keys.flatMap{ Value.pkObject($0) }
}
}
and then used for example:
extension GoogleContact: PrimaryKeyConvertible {...}
extension GoogleContact {
static func fetchGoogleContactsSignal() -> SignalProducer<[GoogleContact], GoogleContactError> { ...}
}
and the call site would be like:
let signal = fetchOnMainThread(GoogleContacts.fetchGoogleContactsSignal()).onNext...
where I would prefer to have it as an extension where it would flow as usual
GoogleContacts
.fetchGoogleContactsSignal()
.fetchOnMainThread()
Update
another version of the function I've tried : (#J.Wang)
extension SignalProducerType
where Value == [PrimaryKeyConvertible]
{
func fetchArrayOnMainThread2<T: PrimaryKeyConvertible>() -> SignalProducer<[T], Error> {
return self
.map{ (values: Self.Value) -> [PrimaryKey] in
return values.map{ $0.pkValue }
}
.deliverOnMainThread()
.map{ (keys: [PrimaryKey]) -> [T] in
return keys.flatMap{ T.pkObject($0) }
}
}
}
let signal =
GoogleContacts
.fetchGoogleContactsSignal()
.fetchArrayOnMainThread2() //(3)
(3) Generates error:
'[PrimaryKeyConvertible]' is not convertible to '[GoogleContact]'
Hmm, although I'm not quite sure what the problem is, but I think the following implementation might be what you want.
extension SignalProducerType where Value == [PrimaryKeyConvertible]
{
func fetchOnMainThread() -> SignalProducer<[PrimaryKey], Error> {
return self.map { value in
value.map { $0.pkValue }
}
}
}
Try this:
extension SignalProducerType where Value == [PrimaryKeyConvertible]
{
func fetchOnMainThread<T: PrimaryKeyConvertible>() -> SignalProducer<[T], Error> {
return self.map { value in
value.map { $0.pkValue }
}.map { keys in
keys.flatMap { T.pkObject($0) }
}
}
}