Swift Combine with Sync - swift

I am new to combine.... I have a function that has a subscriber, and that function will return a value from the publisher, but the function will return an empty value before waiting for sink to complete.... is there a way I can wait for the sink and assign the value to the function return variable, then return the String back to the caller of this function.
func getState() -> String{
var state = ""
let statesub = API.getCheckedState(employeeId: "342344", user: user!)
statesub
.mapError({ (error) -> APIError in // 5
NSLog(error.errorDescription!)
return error
})
.sink(receiveCompletion: { _ in},
receiveValue:{
NSLog("State : \($0.state)")
state = $0.state
})
.store(in: &tokens)
return state

In other programming languages, such as C#, there is the concept of async/await. You declare a part of your program as asynchronous by the keyword async and wait for the result with the await keyword in a non-blocking manner. However, this concept is not implemented in Swift (even tough it is heavily discussed).
So, is there an alternative in Swift? Yes, using a DispatchSemaphore. Your method should look like:
func getState() -> String {
var state = ""
var sema = DispatchSemaphore(value: 0)
let statesub = API.getCheckedState(employeeId: "342344", user: user!)
statesub
.mapError({ (error) -> APIError in // 5
NSLog(error.errorDescription!)
return error
})
.sink(receiveCompletion: { _ in},
receiveValue: {
defer { sema.signal() }
NSLog("State : \($0.state)")
state = $0.state
})
.store(in: &tokens)
sema.wait()
return state
}

Related

Create a publisher that emits a value on completion of another publisher

I have a publisher that never emits items and only completes or fails with an error (AnyPublisher<Never, Error>). I want to transform that publisher into a publisher that emits a value when the first publisher completes (AnyPublisher<Value, Error>), or passes through any error. I want to create that value after completion of the first publisher. I could do something like this, but it seems quite messy:
func demo() -> AnyPublisher<Int, Error> {
// Using Empty just for demo purposes
let firstPublisher = Empty<Never, Error>(completeImmediately: true).eraseToAnyPublisher()
var cancellable: AnyCancellable?
return Future<Int, Error> { promise in
cancellable = firstPublisher
.sink { completion in
switch completion {
case .failure(let error):
promise(.failure(error))
case .finished:
// some operation that generates value
let value:Int = 1
promise(.success(value))
}
} receiveValue: { _ in
}
}
.handleEvents(receiveCancel: {
cancellable?.cancel()
})
.eraseToAnyPublisher()
}
Can this be done a better way? Something like:
extension AnyPublisher {
func completionMap<T, P>(_: (_ completion: Subscribers.Completion<Self.Failure>) -> P) -> P where P: Publisher, T == P.Output, Self.Failure == P.Failure {
/// ???
}
}
func demo() -> AnyPublisher<Int, Error> {
// Using Empty just for demo purposes
let firstPublisher = Empty<Never, Error>(completeImmediately: true).eraseToAnyPublisher()
return firstPublisher
.completionMap { completion -> AnyPublisher<Int, Error> in
switch completion {
case .failure(let error):
return Fail(error: error).eraseToAnyPublisher()
case .finished:
// some operation that generates value
let value:Int = 1
return Just(value).setFailureType(to: Error.self).eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
You could use .append (which returns a Publishers.Concatenate) as a way to emit a value after the first publisher completes.
let firstPublisher: AnyPublisher<Never, Error> = ...
let demo = firstPublisher
.map { _ -> Int in }
.append([1])
The above will emit 1 if firstPublisher completes successfully, or would error out.

Chaining calls when using Future in Swift similar to PromiseKit

Below there are three functions. The first one is the function that I need to refactor. Basically what I'm hoping for is something similar what can be achieved using Promise Kit but in this case using Swifts combine framework.
The second function loginWithFacebook() returns a AuthCredential.
This AuthCredential needs to be passed on to the last functions which returns a type Future<UserProfileCompact, Error> which is a similar return type to the main function (1st function).
My question is is there a way to achieve this in a Swifty way, similar to Promise Kit doing this operation: return loginWithFacebook().then {loginWithFirebase(:_)}
// Call site is a View Model
// Main Function that needs to be refactored
func loginwithFacebook() -> Future<UserProfileCompact, Error> {
//This returs a Future Firebase Credential
loginWithFacebook()
//The above credential needs to be passed to this method and this returns a type Future<UserProfileCompact, Error>
loginWithFirebase(<#T##credentials: AuthCredential##AuthCredential#>)
}
private func loginWithFacebook() -> Future<AuthCredential,Error> {
return Future { [weak self] promise in
self?.loginManager.logIn(permissions: ["public_profile","email"], from: UIViewController()) { (loginResult, error) in
if let error = error {
promise(.failure(error))
} else if loginResult?.isCancelled ?? false {
//fatalError()
}
else if let authToken = loginResult?.token?.tokenString {
let credentials = FacebookAuthProvider.credential(withAccessToken: authToken)
promise(.success(credentials))
}
else{
fatalError()
}
}
}
}
private func loginWithFirebase(_ credentials: AuthCredential) -> Future<UserProfileCompact, Error> {
return Future { promise in
Auth.auth().signIn(with: credentials) { (result, error) in
if let error = error {
//Crashlytics.crashlytics().record(error: error)
promise(.failure(error))
}
else if let user = result?.user {
//Crashlytics.crashlytics().setUserID(user.uid)
let profile = UserProfileCompactMapper.map(firebaseUser: user)
promise(.success(profile))
}
else {
fatalError()
}
}
}
}
You can use a .flatMap operator, which takes a value from upstream and produces a publisher. This would look something like below.
Note, that it's also better to return a type-erased AnyPublisher at the function boundary, instead of the specific publisher used inside the function
func loginwithFacebook() -> AnyPublisher<UserProfileCompact, Error> {
loginWithFacebook().flatMap { authCredential in
loginWithFirebase(authCredential)
}
.eraseToAnyPublisher()
}

Swift Combine Chunk Operator

I'm trying to create chunks of a stream in Apple's Combine framework.
What I'm going for is something like this:
Stream a:
--1-2-3-----4-5--->
Stream b:
--------0-------0->
a.chunk(whenOutputFrom: b)
-------[1, 2, 3]---[4, 5]-->
Can this be implemented in Combine?
What you are looking for is the buffer operator in the ReactiveX world.
There is no built in buffer operator (in the ReactiveX sense) in Combine. The built-in buffer is seems to be more like a bufferCount in ReactiveX.
I found this answer by Daniel T, which recreates the buffer operator in RxSwift, and also this cheatsheet, which tells you how to port RxSwift to Combine.
However, the answer by Daniel T uses Observable.create, which isn't available in Combine. I looked a bit deeper, and found this other answer, that recreates Observable.create in Combine.
Combining the three things I've found (no pun intended), this is what I came up with:
// -------------------------------------------------
// from https://stackoverflow.com/a/61035663/5133585
struct AnyObserver<Output, Failure: Error> {
let onNext: ((Output) -> Void)
let onError: ((Failure) -> Void)
let onCompleted: (() -> Void)
}
struct Disposable {
let dispose: () -> Void
}
extension AnyPublisher {
static func create(subscribe: #escaping (AnyObserver<Output, Failure>) -> Disposable) -> Self {
let subject = PassthroughSubject<Output, Failure>()
var disposable: Disposable?
return subject
.handleEvents(receiveSubscription: { subscription in
disposable = subscribe(AnyObserver(
onNext: { output in subject.send(output) },
onError: { failure in subject.send(completion: .failure(failure)) },
onCompleted: { subject.send(completion: .finished) }
))
}, receiveCancel: { disposable?.dispose() })
.eraseToAnyPublisher()
}
}
// -------------------------------------------------
// -------------------------------------------------
// adapted from https://stackoverflow.com/a/43413167/5133585
extension Publisher {
/// collects elements from the source sequence until the boundary sequence fires. Then it emits the elements as an array and begins collecting again.
func buffer<T: Publisher, U>(_ boundary: T) -> AnyPublisher<[Output], Failure> where T.Output == U {
return AnyPublisher.create { observer in
var buffer: [Output] = []
let lock = NSRecursiveLock()
let boundaryDisposable = boundary.sink(receiveCompletion: {
_ in
}, receiveValue: {_ in
lock.lock(); defer { lock.unlock() }
observer.onNext(buffer)
buffer = []
})
let disposable = self.sink(receiveCompletion: { (event) in
lock.lock(); defer { lock.unlock() }
switch event {
case .finished:
observer.onNext(buffer)
observer.onCompleted()
case .failure(let error):
observer.onError(error)
buffer = []
}
}) { (element) in
lock.lock(); defer { lock.unlock() }
buffer.append(element)
}
return Disposable {
disposable.cancel()
boundaryDisposable.cancel()
}
}
}
}
// -------------------------------------------------
I think you would be interested in Combine collect() method.
there is variation it it as well such as by time, count or both.
.collect(.byTimeOrCount(DispatchQueue.global(), 1.0, 10))
where we pass context -> for example global queue
time to wait for it such as 1s in above example
and the count of 10 elements.
Use case would look something like this:
let bufferSubject = PassthroughSubject<Int, Never>()
let cancelBag = Set<AnyCancellable>()
let subscriber = bufferSubject.eraseToAnyPublisher()
.collect(.byTimeOrCount(DispatchQueue.global(), 1.0, 10))
.sink { value in
print("🚀🚀 value: \(value)")
}
.store(in: &cancelBag)
be sure to test it :)
bufferSubject.send(1)
bufferSubject.send(2)
bufferSubject.send(3)
...
DispatchQueue.asyncAfter(...) {
bufferSubject.send(4)
bufferSubject.send(5)
bufferSubject.send(6)
}

Future Combine sink does not recieve any values

I want to add a value to Firestore. When finished I want to return the added value. The value does get added to Firestore successfully. However, the value does not go through sink.
This is the function that does not work:
func createPremium(user id: String, isPremium: Bool) -> AnyPublisher<Bool,Never> {
let dic = ["premium":isPremium]
return Future<Bool,Never> { promise in
self.db.collection(self.dbName).document(id).setData(dic, merge: true) { error in
if let error = error {
print(error.localizedDescription)
} else {
/// does get called
promise(.success(isPremium))
}
}
}.eraseToAnyPublisher()
}
I made a test function that works:
func test() -> AnyPublisher<Bool,Never> {
return Future<Bool,Never> { promise in
promise(.success(true))
}.eraseToAnyPublisher()
}
premiumRepository.createPremium(user: userID ?? "1234", isPremium: true)
.sink { receivedValue in
/// does not get called
print(receivedValue)
}.cancel()
test()
.sink { recievedValue in
/// does get called
print("Test", recievedValue)
}.cancel()
Also I have a similar code snippet that works:
func loadExercises(category: Category) -> AnyPublisher<[Exercise], Error> {
let document = store.collection(category.rawValue)
return Future<[Exercise], Error> { promise in
document.getDocuments { documents, error in
if let error = error {
promise(.failure(error))
} else if let documents = documents {
var exercises = [Exercise]()
for document in documents.documents {
do {
let decoded = try FirestoreDecoder().decode(Exercise.self, from: document.data())
exercises.append(decoded)
} catch let error {
promise(.failure(error))
}
}
promise(.success(exercises))
}
}
}.eraseToAnyPublisher()
}
I tried to add a buffer but it did not lead to success.
Try to change/remove .cancel() method on your subscriptions. Seems you subscribe to the publisher, and then immediately cancel the subscription. The better option is to retain and store all your subscriptions in the cancellable set.

How to replicate PromiseKit-style chained async flow using Combine + Swift

I was using PromiseKit successfully in a project until Xcode 11 betas broke PK v7. In an effort to reduce external dependencies, I decided to scrap PromiseKit. The best replacement for handling chained async code seemed to be Futures using the new Combine framework.
I am struggling to replicate the simple PK syntax using Combine
ex. simple PromiseKit chained async call syntax
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}
I understand:
A Swift standard library implementation of async/await would solve this problem (async/await does not yet exist, despite lots of chatter and involvement from Chris Latter himself)
I could replicate using Semaphores (error-prone?)
flatMap can be used to chain Futures
The async code I'd like should be able to be called on demand, since it's involved with ensuring user is logged in. I'm wrestling with two conceptual problems.
If I wrap Futures in a method, with sink to handle result, it seems that the method goes out of scope before subscriber is called by sink.
Since Futures execute only once, I worry that if I call the method multiple times I'll only get the old, stale, result from the first call. To work around this, maybe I would use a PassthroughSubject? This allows the Publisher to be called on demand.
Questions:
Do I have to retain every publisher and subscriber outside of the
calling method
How can I replicate simple chained async using the Swift standard library and then embed this in a swift instance method I can call on-demand to restart the chained async calls from the top??
//how is this done using Combine?
func startSync() {
getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}
This is not a real answer to your whole question — only to the part about how to get started with Combine. I'll demonstrate how to chain two asynchronous operations using the Combine framework:
print("start")
Future<Bool,Error> { promise in
delay(3) {
promise(.success(true))
}
}
.handleEvents(receiveOutput: {_ in print("finished 1")})
.flatMap {_ in
Future<Bool,Error> { promise in
delay(3) {
promise(.success(true))
}
}
}
.handleEvents(receiveOutput: {_ in print("finished 2")})
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
First of all, the answer to your question about persistence is: the final subscriber must persist, and the way to do this is using the .store method. Typically you'll have a Set<AnyCancellable> as a property, as here, and you'll just call .store as the last thing in the pipeline to put your subscriber in there.
Next, in this pipeline I'm using .handleEvents just to give myself some printout as the pipeline moves along. Those are just diagnostics and wouldn't exist in a real implementation. All the print statements are purely so we can talk about what's happening here.
So what does happen?
start
finished 1 // 3 seconds later
finished 2 // 3 seconds later
done
So you can see we've chained two asynchronous operations, each of which takes 3 seconds.
How did we do it? We started with a Future, which must call its incoming promise method with a Result as a completion handler when it finishes. After that, we used .flatMap to produce another Future and put it into operation, doing the same thing again.
So the result is not beautiful (like PromiseKit) but it is a chain of async operations.
Before Combine, we'd have probably have done this with some sort of Operation / OperationQueue dependency, which would work fine but would have even less of the direct legibility of PromiseKit.
Slightly more realistic
Having said all that, here's a slightly more realistic rewrite:
var storage = Set<AnyCancellable>()
func async1(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async1")
promise(.success(true))
}
}
func async2(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async2")
promise(.success(true))
}
}
override func viewDidLoad() {
print("start")
Future<Bool,Error> { promise in
self.async1(promise)
}
.flatMap {_ in
Future<Bool,Error> { promise in
self.async2(promise)
}
}
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
As you can see, the idea that is our Future publishers simply have to pass on the promise callback; they don't actually have to be the ones who call them. A promise callback can thus be called anywhere, and we won't proceed until then.
You can thus readily see how to replace the artificial delay with a real asynchronous operation that somehow has hold of this promise callback and can call it when it completes. Also my promise Result types are purely artificial, but again you can see how they might be used to communicate something meaningful down the pipeline. When I say promise(.success(true)), that causes true to pop out the end of the pipeline; we are disregarding that here, but it could be instead a downright useful value of some sort, possibly even the next Future.
(Note also that we could insert .receive(on: DispatchQueue.main) at any point in the chain to ensure that what follows immediately is started on the main thread.)
Slightly neater
It also occurs to me that we could make the syntax neater, perhaps a little closer to PromiseKit's lovely simple chain, by moving our Future publishers off into constants. If you do that, though, you should probably wrap them in Deferred publishers to prevent premature evaluation. So for example:
var storage = Set<AnyCancellable>()
func async1(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async1")
promise(.success(true))
}
}
func async2(_ promise:#escaping (Result<Bool,Error>) -> Void) {
delay(3) {
print("async2")
promise(.success(true))
}
}
override func viewDidLoad() {
print("start")
let f1 = Deferred{Future<Bool,Error> { promise in
self.async1(promise)
}}
let f2 = Deferred{Future<Bool,Error> { promise in
self.async2(promise)
}}
// this is now extremely neat-looking
f1.flatMap {_ in f2 }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
matt's answer is correct, use flatMap to chain promises. I got in the habit of returning promises when using PromiseKit, and carried it over to Combine (returning Futures).
I find it makes the code easier to read. Here's matt's last example with that recommendation:
var storage = Set<AnyCancellable>()
func async1() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async1")
promise(.success(true))
}
}
}
func async2() -> Future<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}
}
override func viewDidLoad() {
print("start")
async1()
.flatMap { _ in async2() }
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
.store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}
Note that AnyPublisher will work as a return value as well, so you could abstract away the Future and have it return AnyPublisher<Bool, Error> instead:
func async2() -> AnyPublisher<Bool, Error> {
Future { promise in
delay(3) {
print("async2")
promise(.success(true))
}
}.eraseToAnyPubilsher()
}
Also if you want to use the PromiseKit-like syntax, here are some extensions for Publisher
I am using this to seamlessly switch from PromiseKit to Combine in a project
extension Publisher {
func then<T: Publisher>(_ closure: #escaping (Output) -> T) -> Publishers.FlatMap<T, Self>
where T.Failure == Self.Failure {
flatMap(closure)
}
func asVoid() -> Future<Void, Error> {
return Future<Void, Error> { promise in
let box = Box()
let cancellable = self.sink { completion in
if case .failure(let error) = completion {
promise(.failure(error))
} else if case .finished = completion {
box.cancellable = nil
}
} receiveValue: { value in
promise(.success(()))
}
box.cancellable = cancellable
}
}
#discardableResult
func done(_ handler: #escaping (Output) -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: {compl in
if case .finished = compl {
box.cancellable = nil
}
}, receiveValue: {
handler($0)
})
box.cancellable = cancellable
return self
}
#discardableResult
func `catch`(_ handler: #escaping (Failure) -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: { compl in
if case .failure(let failure) = compl {
handler(failure)
} else if case .finished = compl {
box.cancellable = nil
}
}, receiveValue: { _ in })
box.cancellable = cancellable
return self
}
#discardableResult
func finally(_ handler: #escaping () -> Void) -> Self {
let box = Box()
let cancellable = self.sink(receiveCompletion: { compl in
if case .finished = compl {
handler()
box.cancellable = nil
}
}, receiveValue: { _ in })
box.cancellable = cancellable
return self
}
}
fileprivate class Box {
var cancellable: AnyCancellable?
}
And here's an example of use:
func someSync() {
Future<Bool, Error> { promise in
delay(3) {
promise(.success(true))
}
}
.then { result in
Future<String, Error> { promise in
promise(.success("111"))
}
}
.done { string in
print(string)
}
.catch { err in
print(err.localizedDescription)
}
.finally {
print("Finished chain")
}
}
You can use this framework for Swift coroutines, it's also can be used with Combine - https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let future: Future<Bool, Error>
let coFuture = future.subscribeCoFuture()
let bool = try coFuture.await()
}