ebg
August 30, 2018, 10:13pm
#1
I'm trying to figure out if there's a functional way to do this in pure Swift, without using something like semaphores or dispatch groups:
I have an array of things, items: [Item]
, and for each item in the array I want to execute an asynchronous method, func asyncMethod
.
I need to execute the methods dependently (so only execute the next item after the previous asyncMethod
has called its completion block). If the asyncMethod
is ever successful, exit the loop of items early.
I have this working in ugly fashion using DispatchGroup
, but wondering if there's a Swiftier way here.
1 Like
shanayyy
(Yang)
August 31, 2018, 3:11am
#2
import PlaygroundSupport
import UIKit
PlaygroundPage.current.needsIndefiniteExecution = true
extension Sequence {
func asyncEach(_ body: @escaping (Self.Element,_ didFinish: @escaping () -> Void) -> Void, completion: @escaping () -> Void) {
func executeNext(_ iterator: Iterator) {
var iterator = iterator
if let next = iterator.next() {
body(next) {
executeNext(iterator)
}
}else {
completion()
}
}
executeNext(self.makeIterator())
}
func asyncEach(_ body: @escaping (Self.Element) -> Void, completion: @escaping () -> Void) {
let execQueue = DispatchQueue.init(label: "SerialQueue")
for e in self {
execQueue.async {
body(e)
}
}
execQueue.async {
DispatchQueue.main.async(execute: completion)
}
}
}
class Item {
let order: Int
init(order: Int) { self.order = order }
func syncMethod() {
print("isMainthread: \(Thread.current.isMainThread) \(order) start")
sleep(1)
}
func asyncMethod(completion: @escaping () -> Void) {
let order = self.order
DispatchQueue.init(
label: "ConcurrentQueue",
attributes: .concurrent)
.async {
print("isMainthread: \(Thread.current.isMainThread) \(order) start")
sleep(1)
completion()
}
}
}
let items = [0,1,2,3,4,5].map( Item.init(order:) )
/*
items.asyncEach({ (item) in
item.syncMethod()
print("isMainthread: (Thread.current.isMainThread) (item.order) done")
}, completion: {
print("isMainthread: (Thread.current.isMainThread) all done\n\n")
})
*/
items.asyncEach({ (item, didFinish) in
item.asyncMethod(completion:{
print("isMainthread: (Thread.current.isMainThread) (item.order) done")
didFinish()
})
}, completion: {
print("isMainthread: (Thread.current.isMainThread) all done\n\n")
})
like this?
1 Like
Here's some approaches. However, this doesn't really scale with Swift's current type system. You can always write a special case like those below that fits best, but you won't be able to further abstract over that to express monadic sequencing in general.
'Brute-force'
class AsyncSequence<T, U> {
typealias CallBack<T> = (T) -> ()
static func exec(async: @escaping (T, @escaping CallBack<U>) -> (),
for sequence: [T],
completion: @escaping CallBack<U>) {
if let _ = sequence.first {
exec(async: async, for: sequence, index: 0, completion: completion)
}
}
static private func exec(async: @escaping (T, @escaping CallBack<U>) -> (),
for sequence: [T], index: Int,
completion: @escaping CallBack<U>) {
async(sequence[index]) { response in
completion(response)
if (index < sequence.count - 1) {
exec(async: async, for: sequence, index: index + 1, completion: completion)
}
}
}
}
// Test
func doSomethingIn2Seconds(with number: Int, completion: @escaping (Int) -> ()) {
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
completion(number)
}
}
AsyncSequence.exec(async: doSomethingIn2Seconds, for: [2, 3, 4, 5, 6]) { number in
print(number)
}
Promises
class Promise<T> {
private var completion: ((T) -> Void)?
func fulfill(_ arg: T) { completion?(arg) }
func then<U>(_ body: @escaping (T) -> Promise<U>) -> Promise<U> {
var promise = Promise<U>()
completion = { value in
let next = body(value)
next.completion = promise.completion
promise = next
}
return promise
}
}
// Test
func doSomethingIn2Seconds(with number: Int) -> Promise<Int> {
let promise: Promise<Int> = Promise()
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
promise.fulfill(number)
}
return promise
}
var promise = doSomethingIn2Seconds(with: 0)
for element in [1, 2, 3, 4, 5, 6, 7] {
promise = promise.then { number in
print(number)
return doSomethingIn2Seconds(with: element)
}
}
1 Like