Why this produces compiler error in Combine? - ios

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).

Related

Using Swift Task inside a flatmap for async/await

I'm trying to receive an async function return value inside a flatMap and wrap it under a Task to allow for async functionality but I'm having this error when I'm trying to access the Task value:
'async' property access in a function that does not support concurrency
How do I go about returning the value?
Playground
import UIKit
public func isEvenNumber(num:(Int)) async -> Result<Int, Error> {
if num%2 == 0 {
print("EVEN")
return .success(1)
}
print("ODD")
return .success(0)
}
func profileAsyncFunc() async -> Result<Bool, Error> {
return await isEvenNumber(num: 3)
.flatMap{ _ -> Result<Bool,Error> in
Task {
return await testAsyncFunc()
}.value
}
}
func testAsyncFunc() async -> Result<Bool, Error> {
let basicTask = Task { () -> Result<Bool, Error> in
.success(true)
}
return await basicTask.value
}
Task {
await profileAsyncFunc()
}
A few observations:
Swift concurrency simplifies this quite a bit. No flatMap is needed.
The isEvenNumber is not really asynchronous unless you await something. Adding async qualifier does not make it asynchronous. It only means that you could add some asynchronous code in the function, but only if you await something within the function.
In Swift concurrency, the Result type is no longer needed and quickly becomes syntactic noise. In an async function, you simply either return a value, or throw an error.
Let us assume that isEvenNumber is just a placeholder for something sufficiently complicated that you did need to make it asynchronous to avoid blocking the current actor. Furthermore, while you do not throw errors, let us assume that this is a proxy for some method that would.
If all of that were the case, you would need to make sure that you get it off the current actor with a detached task, and then it would be asynchronous, as you would await its value. And I am arbitrarily defining isEven to throw an error if the value is negative. (Clearly, this is not a reasonable reason to throw an error, but included it for illustrative purposes.)
Thus:
enum NumberError: Error {
case negative
}
// This is not sufficiently computationally intensive to warrant making it run
// asynchronously, but let us assume that it was. You would do something like:
func isEven(_ num: Int) async throws -> Bool {
if num < 0 { throw NumberError.negative }
return await Task.detached {
num.isMultiple(of: 2)
}.value
}
func profileAsyncFunc() async throws -> Bool {
try await isEven(3)
}
Then you could do:
Task {
let isEven = try await profileAsyncFunc()
print(isEven)
}
You said:
I'm trying to receive an async function return value inside a flatMap …
Why? You do not need flatMap in Swift concurrency, as you might use in Combine.
If you are simply trying to adopt async-await, you can just excise this from your code. If there is some other problem that you are trying to solve by introducing flatMap, please edit the question providing a more complete example, and explain the intended rationale for flatMap.
As it stands, the example is sufficiently oversimplified that it makes it hard for us to understand the problem you are really trying to solve.

Run 3 observables sequentially, using result from first in the last one

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)
}
}
}

How to use swift-4 Promises then, done, catch and other blocks

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)
}
}

Return a completable in RxSwift without using a create block

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) }

Adapting an async method with closure to a syncronous method with return type

I'm new to swift and not overly experienced in any major threading work, so I'm try to improve my skills a bit, hoping for a bit of help or guidance. This is one concept I can't seem to figure out.
I have a class which does communication via an Input and Output stream, the app sends via the output stream, and reads the result from the input stream. Right now I have created the method to send async via callbacks, but I would like the ability to send synchronously, and handle the threading at a higher level.
Currently my communication method looks something like this:
typealias CommunicationCallback = ((CommunicationResult) -> Void)
func sendAsync(send message: String, closure: #escaping CommunicationCallback) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else {
return
}
let messagePair = MessagePair(SendReceiveResult(sent: message, received: nil),
closure)
strongSelf.writeQueue.enqueue(messagePair)
strongSelf.writeFromQueue() //Calls the CommunicationCallback closure after it has read result
}
}
func sendSyncronized(send message: String) -> CommunicationResult {
???
}
Is there a general way to wrap a async all like that above?
In case I am thinking of this totally wrong, what Id' like in some cases to simply be able to call
let result = sendSyncronized("foo")
instead of doing
send("foo", closure: { result in
switch result {
case .a:
foo()
base .b:
bar()
}
})
everytime, as there are some cases in which I need to do many sequential writes while waiting for the previous result.
Any help is welcome!
Maybe you can use an optional return type.
typealias CommunicationCallback = ((CommunicationResult) -> Type?)
return nil in async method and the value in synchronous method. Use if let to check if the optional binding is successful. If not, you need to get the value from closure anyway.

Resources