Capturing values from synchronous and asynchronous closures - swift

Assume a simple struct like this:
struct Foo {
let value: Int
let delay: TimeInterval
}
It has 2 functions, each taking a closure as a parameter. One is synchronously called, the other asynchronously after delay:
extension Foo {
func sync(_ completion: (Int) -> Void) {
completion(value)
}
}
extension Foo {
func async(_ completion: #escaping (Int) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(delay)) {
completion(self.value)
}
}
}
Now assume I have an array of Foo objects:
let foo1 = Foo(value: 1, delay: 1)
let foo2 = Foo(value: 2, delay: 0)
Now I want to query each of them to get the values they would supply and put those into an array. Synchronously, it can be done like this:
let syncValues = [foo1, foo2].reduce(into: []) { values, foo in foo.sync { values.append($0) } } // [1, 2]
Asynchronously, I want to do this and have foo2 report before foo1 does:
let asyncValues = [foo1, foo2].reduce(into: []) { values, foo in foo.async { values.append($0) } // [2, 1]}
However, I get an error when I try to do the asynchronous version:
Escaping closure captures 'inout' parameter 'values'
What is a good way to perform this asynchronous task?

Because it's async, you can't do this in a single synchronous statement. Assuming you understand that, using Combine would probably be easiest:
import Combine
let fooResultsPublishers = [foo1, foo2].publisher
.flatMap { foo in
Future { promise in
foo.async { promise(.success($0)) }
}
}
.collect()
To actually get the value, you need to use .sink to act on it asynchronously:
fooResultsPublishers
.sink {
print($0) // [2,1]
}
// store it in long-living property, like
// var cancellables: Set<AnyCancellable> = []
.store(in: &cancellables)
Without Combine, you'd need to use something like a DispatchGroup:
let group = DispatchGroup()
var values: [Int] = []
[foo1, foo2].forEach { foo in
group.enter()
foo.async { values.append($0); group.leave() }
}
group.notify(queue: .main) {
print(values) // [2,1]
}

Related

How to best create a publisher aggregate of #Published values in Combine?

Given an hierarchical structure of #OberservableObjects - I often find myself in a situation where I need a publisher which provides some kind of updated aggregate of the entire structure (the example below calculates a sum, but it could be anything)
Below is the solution I have come up with - which kinda works, but also not... :)
Problem #1: It looks way to complicated - and I feel I am missing something...
Problem #2: It does not work as the $foo publisher on top does emit changes to foo before foo changes, which are then not present in the second self.$foo publisher (which shows the old state).
Sometimes I need the aggregate in sync with swiftUI view updates - so that I need to utilize the #Published value and no separate publisher that emits during didSet of the variable.
I did not find a good solution... So how would you guys resolve this?
class Foo:ObservableObject {
#Published var bar:Int = 0
}
class Foobar:ObservableObject {
#Published var foo:[Foo] = []
var sumPublisher:AnyPublisher<Int,Never> {
// Whenever the foo array or one of the foo.bar values change
//
$foo
.map { fooArray in
Publishers.MergeMany( fooArray.map { foo in foo.$bar } )
}
.switchToLatest()
// Calclulate a new sum by collecting and reducing all foo.bar values.
//
.map { [unowned self] _ in
self.$foo // <--- in case of a foo change, this is still the unchanged foo, therefore not correct.
.map { fooArray -> AnyPublisher<Int,Never> in
Publishers.MergeMany( fooArray.map { foo in foo.$bar.first() } )
.collect()
.map { barArray -> Int in
barArray.reduce(0, { $0 + $1 })
}
.eraseToAnyPublisher()
}
.switchToLatest()
}
.switchToLatest()
.removeDuplicates()
.eraseToAnyPublisher()
}
}
Problem #2 : #Published fire signals on "willSet" and not "didSet".
You can use this extension :
extension Published.Publisher {
var didSet: AnyPublisher<Value, Never> {
self.receive(on: RunLoop.main).eraseToAnyPublisher()
}
}
and
self.$foo.didSet
.map { _ in
//...//
}
Problem #1 :
Maybe so :
class Foobar:ObservableObject {
#Published var foo:[Foo] = []
#Published var sum = 0
var cancellable: AnyCancellable?
init() {
cancellable =
sumPublisher
.sink {
self.sum = $0
}
}
var sumPublisher: AnyPublisher<Int,Never> {
let firstPublisher = $foo.didSet
.flatMap {array in
array.publisher
.flatMap { $0.$bar.didSet }
.map { _ -> [Foo] in
return self.foo
}
}
.eraseToAnyPublisher()
let secondPublisher = $foo.didSet
.dropFirst(1)
return Publishers.Merge(firstPublisher, secondPublisher)
.map { barArray -> Int in
return barArray
.map {$0.bar}
.reduce(0, { $0 + $1 })
}
.removeDuplicates()
.eraseToAnyPublisher()
}
}
And to test :
struct FooBarView: View {
#StateObject var fooBar = Foobar()
var body: some View {
VStack {
HStack {
Button("Change list") {
fooBar.foo = (1 ... Int.random(in: 5 ... 9)).map { _ in Int.random(in: 1 ... 9) }.map(Foo.init)
}
Text(fooBar.sum.description)
Button("Change element") {
let idx = Int.random(in: 0 ..< fooBar.foo.count)
fooBar.foo[idx].bar = Int.random(in: 1 ... 9)
}
}
List(fooBar.foo, id: \.bar) { foo in
Text(foo.bar.description)
}
.onAppear {
fooBar.foo = [1, 2, 3, 8].map(Foo.init)
}
}
}
}
EDIT :
If you really prefer to use #Published (the willSet publisher), it sends the new value of bar therefore you could deduce the new value of foo (the array) :
var sumPublisher: AnyPublisher<Int, Never> {
let firstPublisher = $foo
.flatMap { array in
array.enumerated().publisher
.flatMap { index, value in
value.$bar
.map { (index, $0) }
}
.map { index, value -> [Foo] in
var newArray = array
newArray[index] = Foo(bar: value)
return newArray
}
}
.eraseToAnyPublisher()
let secondPublisher = $foo
.dropFirst(1)
return Publishers.Merge(firstPublisher, secondPublisher)
.map { barArray -> Int in
barArray
.map { $0.bar }
.reduce(0, { $0 + $1 })
}
.removeDuplicates()
.eraseToAnyPublisher()
}
By far, the easiest approach here is to use a struct instead of a class with #Published:
struct Foo {
var bar: Int = 0
}
Then you can simply create a computed property:
class Foobar: ObservableObject {
#Published var foo: [Foo] = []
var sum: Int {
foo.map(\.bar).reduce(0, +)
}
// ...
}
For SwiftUI views, you wouldn't even need to make it a Publisher - when foo changes, because it's #Published, it will cause the View to access sum again, which would give it the recomputed value.
If you insist on it being a Publisher, it's still easy to do, since foo itself changes when any of its values Foo change (since they are value-type structs):
var sumPublisher: AnyPublisher<Int, Never> {
self.$foo
.map { $0.map(\.bar).reduce(0, +) }
.eraseToAnyPublisher()
}
Sometimes, it's not possible to change a class into a struct for whatever reason (maybe each class has its own life cycle that self-updates). Then you'd need to manually keep track of all the additions/removals of Foo objects in the array (via willSet or didSet), and subscribe to changes in their foo.bar.

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

Loop over Publisher Combine framework

I have the following function to perform an URL request:
final class ServiceManagerImpl: ServiceManager, ObservableObject {
private let session = URLSession.shared
func performRequest<T>(_ request: T) -> AnyPublisher<String?, APIError> where T : Request {
session.dataTaskPublisher(for: self.urlRequest(request))
.tryMap { data, response in
try self.validateResponse(response)
return String(data: data, encoding: .utf8)
}
.mapError { error in
return self.transformError(error)
}
.eraseToAnyPublisher()
}
}
Having these 2 following functions, I can now call the desired requests from corresponded ViewModel:
final class AuditServiceImpl: AuditService {
private let serviceManager: ServiceManager = ServiceManagerImpl()
func emptyAction() -> AnyPublisher<String?, APIError> {
let request = AuditRequest(act: "", nonce: String.randomNumberGenerator)
return serviceManager.performRequest(request)
}
func burbleAction(offset: Int) -> AnyPublisher<String?, APIError> {
let request = AuditRequest(act: "burble", nonce: String.randomNumberGenerator, offset: offset)
return serviceManager.performRequest(request)
}
}
final class AuditViewModel: ObservableObject {
#Published var auditLog: String = ""
private let auditService: AuditService = AuditServiceImpl()
init() {
let timer = Timer(timeInterval: 5, repeats: true) { _ in
self.getBurbles()
}
RunLoop.main.add(timer, forMode: .common)
}
func getBurbles() {
auditService.emptyAction()
.flatMap { [unowned self] offset -> AnyPublisher<String?, APIError> in
let currentOffset = Int(offset?.unwrapped ?? "") ?? 0
return self.auditService.burbleAction(offset: currentOffset)
}
.receive(on: RunLoop.main)
.sink(receiveCompletion: { [unowned self] completion in
print(completion)
}, receiveValue: { [weak self] burbles in
self?.auditLog = burbles!
})
.store(in: &cancellableSet)
}
}
Everything is fine when I use self.getBurbles() for the first time. However, for the next calls, print(completion) shows finished, and the code doesn't perform self?.auditLog = burbles!
I don't know how can I loop over the getBurbles() function and get the response at different intervals.
Edit
The whole process in a nutshell:
I call getBurbles() from class initializer
getBurbles() calls 2 nested functions: emptyAction() and burbleAction(offset: Int)
Those 2 functions generate different requests and call performRequest<T>(_ request: T)
Finally, I set the response into auditLog variable and show it on the SwiftUI layer
There are at least 2 issues here.
First when a Publisher errors it will never produce elements again. That's a problem here because you want to recycle the Publisher here and call it many times, even if the inner Publisher fails. You need to handle the error inside the flatMap and make sure it doesn't propagate to the enclosing Publisher. (ie you can return a Result or some other enum or tuple that indicates you should display an error state).
Second, flatMap is almost certainly not what you want here since it will merge all of the api calls and return them in arbitrary order. If you want to cancel any existing requests and only show the latest results then you should use .map followed by switchToLatest.

Grand Central Dispatch for complex flow?

I have a, b, c, d, e time consuming task functions with completion handler.
There are constraints between them:
Both b & c wait for a to finish
The last task e waits for b & c & d to finish
if there is no task d, I could write code in swift like this (not tested yet)
let group = DispatchGroup()
group.enter()
a() { group.leave() }
group.wait()
group.enter()
b() { group.leave() }
group.enter()
c() { group.leave() }
group.notify(queue: .main) {
e()
}
How to add task d without waiting a to complete?
Edited on 4/30 10:00 (+8)
Code Different said
the easiest approach is to make the download function synchronous and add a warning to its documentation that it should never be called from the main thread.
So I made a version based on it. This way cannot handle the return values from concurrent calls. But it looks really like async/await. So I'm satisfied now. Thank you guys.
the async/await like part is
myQueue.async {
downloadSync("A")
downloadSync("B", isConcurrent: true)
downloadSync("C", isConcurrent: true)
downloadSync("D", 4, isConcurrent: true)
waitConcurrentJobs()
downloadSync("E")
}
And the full code is below.
let myGroup = DispatchGroup()
let myQueue = DispatchQueue(label: "for Sync/Blocking version of async functions")
func waitConcurrentJobs() {
myGroup.wait()
}
// original function (async version, no source code)
func download(_ something: String, _ seconds: UInt32 = 1, completionHandler: #escaping ()->Void = {}) {
print("Downloading \(something)")
DispatchQueue.global().async {
sleep(seconds)
print("\(something) is downloaded")
completionHandler()
}
}
// wrapped function (synced version)
// Warning:
// It blocks current thead !!!
// Do not call it on main thread
func downloadSync(
_ something: String,
_ seconds: UInt32 = 1,
isConcurrent: Bool = false
){
myGroup.enter()
download(something, seconds) { myGroup.leave() }
if !isConcurrent {
myGroup.wait()
}
}
// Now it really looks like ES8 async/await
myQueue.async {
downloadSync("A")
downloadSync("B", isConcurrent: true)
downloadSync("C", isConcurrent: true)
downloadSync("D", 4, isConcurrent: true)
waitConcurrentJobs()
downloadSync("E")
}
results
Edit: the easiest approach is to make the download function synchronous and add a warning to its documentation that it should never be called from the main thread. The pyramid of doom for async function is the reason why coroutines were proposed, by no other than Chris Lattner, Swift's creator. As of April 2018, it's not yet a formal proposal waiting for review so chances are that you won't see it in Swift 5.
An synchronous download function:
// Never call this from main thread
func download(_ something: String, _ seconds: UInt32 = 1, completionHandler: #escaping ()->Void = {}) {
let group = DispatchGroup()
print("Downloading \(something)")
group.enter()
DispatchQueue.global().async {
sleep(seconds)
print("\(something) is downloaded")
completionHandler()
group.leave()
}
group.wait()
}
And NSOperation / NSOperationQueue setup:
let opA = BlockOperation() {
self.download("A")
}
let opB = BlockOperation() {
self.download("B")
}
let opC = BlockOperation() {
self.download("C")
}
let opD = BlockOperation() {
self.download("D", 4)
}
let opE = BlockOperation() {
self.download("E")
}
opB.addDependency(opA)
opC.addDependency(opA)
opE.addDependency(opB)
opE.addDependency(opC)
opE.addDependency(opD)
let operationQueue = OperationQueue()
operationQueue.addOperations([opA, opB, opC, opD, opE], waitUntilFinished: false)
Your original effort seems very close to me. You could make a minor adjustment: make B, C, and D be the group that finishes to trigger E.
A could be another group, but since it's one task, I don't see the point. Trigger B and C when it's done.
Note that unlike some of the example code in your question and other answers, in the code below, D and A can both start right away and run in parallel.
let q = DispatchQueue(label: "my-queue", attributes: .concurrent)
let g = DispatchGroup()
func taskA() { print("A") }
func taskB() { print("B"); g.leave() }
func taskC() { print("C"); g.leave() }
func taskD() { print("D"); g.leave() }
func taskE() { print("E") }
g.enter()
g.enter()
g.enter()
q.async {
taskA()
q.async(execute: taskB)
q.async(execute: taskC)
}
q.async(execute: taskD)
g.notify(queue: q, execute: taskE)
You can use this framework to implement async/await pattern - https://github.com/belozierov/SwiftCoroutine
When you call await it doesnโ€™t block the thread but only suspends coroutine, so you can use it in the main thread as well.
func awaitAPICall(_ url: URL) throws -> String? {
let future = URLSession.shared.dataTaskFuture(for: url)
let data = try future.await().data
return String(data: data, encoding: .utf8)
}
func load(url: URL) {
DispatchQueue.main.startCoroutine {
let result1 = try self.awaitAPICall(url)
let result2 = try self.awaitAPICall2(result1)
let result3 = try self.awaitAPICall3(result2)
print(result3)
}
}
I would like to show an alternative solution using Scala like futures:
let result = funcA().flatMap { resultA in
return [funcB(param: resultA.0),
funcC(param: resultA.1),
funcD()]
.fold(initial: [String]()) { (combined, element) in
return combined + [element]
}
}.flatMap { result in
return funcE(param: result)
}.map { result in
print(result)
}
That's it basically. It handles errors (implicitly) and is thread-safe. No Operation subclasses ;)
Note, that funcD will be called only when A completes successfully. Since funcA() can fail it would make no sense to call it. But the code can be easily adapted to make this possible as well, iff required.
Please compare this to function foo() from my other solution which uses Dispatch Groups and Dispatch Queues.
Below an example of the definitions of the async functions which each passing their result to the next one:
func funcA() -> Future<(String, String)> {
print("Start A")
let promise = Promise<(String, String)>()
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
print("Complete A")
promise.complete(("A1", "A2"))
}
return promise.future
}
func funcB(param: String) -> Future<String> {
print("Start B")
let promise = Promise<String>()
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
print("Complete B")
promise.complete("\(param) -> B")
}
return promise.future
}
func funcC(param: String) -> Future<String> {
print("Start C")
let promise = Promise<String>()
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
print("Complete C")
promise.complete("\(param) -> C")
}
return promise.future
}
func funcD() -> Future<String> {
print("Start D")
let promise = Promise<String>()
DispatchQueue.global().asyncAfter(deadline: .now() + 4) {
print("Complete D")
promise.complete("D")
}
return promise.future
}
func funcE(param: [String]) -> Future<String> {
print("Start E")
let promise = Promise<String>()
DispatchQueue.global().asyncAfter(deadline: .now() + 4) {
print("Complete E")
promise.complete("\(param) -> E")
}
return promise.future
}
Which prints this to the console:
Start A
Complete A
Start B
Start C
Start D
Complete B
Complete C
Complete D
Start E
Complete E
["A1 -> B", "A2 -> C", "D"] -> E
Hint: there are a couple of Future and Promise libraries available.

How can I know that a completion block didn't executed but method finished?

How can I programmatically catch the case when completion does not execute?
I can not modify exists method, because this is a simple replacer for a Firebase observe.
func exists(completion: (_ a: Int) -> ()) {
//async call with a completion handler where I get the `a` value
if a % 2 == 0 {
completion(a)
}
..............//other cases
}
func check() {
exists { a in
print(a)
}
}
I thought of some flag, but how do I know that exists ended?
There's many ways of doing what you're trying to do, you could set flags (booleans), you could use optionals in the completion closure, you can use two closures...
I'm posting a way which I find the nicest, but it's purely subjective.
You could change the completion closure argument to be a Result enum for example.
enum Result {
case .success(Int)
case .failure
}
Then in the completion closure, you would replace the argument with this enum.
func exists(completion: (_ result: Result) -> ()) {
let a = arc4random()
if a % 2 == 0 {
completion(.success(a))
} else {
//other cases
completion(.failure)
}
}
func check() {
exists { result in
switch result {
case .succeess(let number):
print(number)
case .failure:
print("Finished without number")
}
}
}
Best way to achieve what you want is to use DisptachGroup.
func exists(completion: (_ a: Int) -> ()) {
completion(1)
}
let group = DispatchGroup()
group.enter()
exists { (a) in
group.leave()
}
group.notify(queue: .main) {
print("Did finish 'exists' function!")
}