Created
April 27, 2020 22:39
-
-
Save ccwasden/2204f2045df38322a161cda3927cf135 to your computer and use it in GitHub Desktop.
Await+Threading
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Author: Chase Wasden | |
// Website: https://gist.github.com/ccwasden | |
// Licensed under MIT license https://opensource.org/licenses/MIT | |
// | |
import Foundation | |
import Combine | |
// Threading | |
typealias Callback<T> = (Result<T, Error>) -> Void | |
enum BackgroundMode { | |
case currentIfBackground | |
case forceBackground | |
case serial | |
} | |
protocol Threading { | |
var scheduler: AnyScheduler<DispatchQueue.SchedulerTimeType, DispatchQueue.SchedulerOptions> { get } | |
var mainScheduler: AnyScheduler<DispatchQueue.SchedulerTimeType, DispatchQueue.SchedulerOptions> { get } | |
func background(_ errorHandler: @escaping (Error) -> Void, mode: BackgroundMode, execute: @escaping () throws -> Void) | |
func background<T>(_ handler: @escaping Callback<T>, mode: BackgroundMode, execute: @escaping () throws -> T) | |
func background(mode: BackgroundMode, execute: @escaping () -> Void) | |
func main(execute: @escaping () -> Void) | |
} | |
extension Threading { | |
func background(_ errorHandler: @escaping (Error) -> Void, execute: @escaping () throws -> Void) { | |
self.background(errorHandler, mode: .currentIfBackground, execute: execute) | |
} | |
func background<T>(_ handler: @escaping Callback<T>, execute: @escaping () throws -> T) { | |
self.background(handler, mode: .currentIfBackground, execute: execute) | |
} | |
func background(execute: @escaping () -> Void) { | |
self.background(mode: .currentIfBackground, execute: execute) | |
} | |
} | |
extension Threading { | |
func main<T, E: Error>(_ fn: @escaping (@escaping (Result<T, E>) -> Void) -> Void, | |
callback: @escaping (Result<T, E>) -> Void) { | |
fn { result in | |
self.main { | |
callback(result) | |
} | |
} | |
} | |
func main<A, T, E: Error>(_ fn: @escaping (A, @escaping (Result<T, E>) -> Void) -> Void, | |
_ a: A, | |
callback: @escaping (Result<T, E>) -> Void) { | |
fn(a) { result in | |
self.main { | |
callback(result) | |
} | |
} | |
} | |
} | |
class DispatchThreading: Threading { | |
let dispatchQueue: DispatchQueue | |
let main: DispatchQueue | |
var scheduler: AnyScheduler<DispatchQueue.SchedulerTimeType, DispatchQueue.SchedulerOptions> { | |
AnyScheduler(dispatchQueue) | |
} | |
var mainScheduler: AnyScheduler<DispatchQueue.SchedulerTimeType, DispatchQueue.SchedulerOptions> { | |
AnyScheduler(main) | |
} | |
init(label: String? = nil) { | |
if let label = label { | |
dispatchQueue = DispatchQueue(label: label, attributes: .concurrent) | |
} | |
else { | |
dispatchQueue = DispatchQueue.global(qos: .background) | |
} | |
main = .main | |
} | |
func background(_ errorHandler: @escaping (Error) -> Void, mode: BackgroundMode, execute: @escaping () throws -> Void) { | |
if Thread.isMainThread || mode != .currentIfBackground { | |
dispatchQueue.async(flags: mode == .serial ? .barrier : []) { | |
do { try execute() } | |
catch { errorHandler(error) } | |
} | |
} | |
else { | |
do { try execute() } | |
catch { errorHandler(error) } | |
} | |
} | |
func background<T>(_ handler: @escaping Callback<T>, mode: BackgroundMode, execute: @escaping () throws -> T) { | |
self.background({ handler(.failure($0)) }, mode: mode) { | |
handler(.success(try execute())) | |
} | |
} | |
func background(mode: BackgroundMode, execute: @escaping () -> Void) { | |
if Thread.isMainThread || mode != .currentIfBackground { | |
dispatchQueue.async(flags: mode == .serial ? .barrier : [], execute: execute) | |
} | |
else { | |
execute() | |
} | |
} | |
func main(execute: @escaping () -> Void) { | |
if Thread.isMainThread { | |
execute() | |
} | |
else { | |
main.async(execute: execute) | |
} | |
} | |
} | |
// Await | |
enum WaitError: Error { | |
case timeout | |
} | |
extension String { | |
var filename: String { | |
return components(separatedBy: "?") | |
.first.flatMap { | |
$0.components(separatedBy: "/").last | |
} ?? self | |
} | |
} | |
fileprivate let DEFAULT_TIMEOUT: TimeInterval = 20 | |
func _wait<T, E: Error>(file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
timeoutAfter: TimeInterval? = DEFAULT_TIMEOUT, | |
for closure: @escaping (@escaping (Result<T, E>) -> Void) throws -> Void) throws -> T { | |
do { | |
let group = DispatchGroup() | |
var value: Result<T, E>? | |
group.enter() | |
try closure { t in | |
value = t | |
group.leave() | |
} | |
if let timeout = timeoutAfter { | |
_ = group.wait(timeout: .now() + timeout) | |
} | |
else { | |
group.wait() | |
} | |
if let v = value { return try v.get() } | |
throw WaitError.timeout | |
} | |
catch { | |
#if DEBUG | |
print("--------- THROWING ERROR -----------") | |
print("TRACE: \(function) \(file.filename):\(line)") | |
print("ERROR: \((error as NSError).domain).\(error as Any)") | |
print("------------------------------------") | |
#endif | |
throw error | |
} | |
} | |
func wait<T>(file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
timeoutAfter: TimeInterval? = DEFAULT_TIMEOUT, | |
for closure: @escaping (@escaping (Result<T, Error>) -> Void) throws -> Void) throws -> T { | |
try _wait(file: file, function: function, line: line, timeoutAfter: timeoutAfter) { fn in try closure(fn) } | |
} | |
func wait<T, E: Error>(file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
timeoutAfter: TimeInterval? = DEFAULT_TIMEOUT, | |
for closure: @escaping (@escaping (Result<T, E>) -> Void) throws -> Void) throws -> T { | |
try _wait(file: file, function: function, line: line, timeoutAfter: timeoutAfter) { fn in try closure(fn) } | |
} | |
func wait<E: Error>(file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
timeoutAfter: TimeInterval? = DEFAULT_TIMEOUT, | |
for closure: @escaping (@escaping (Result<Void, E>) -> Void) throws -> Void) throws { | |
try _wait( | |
file: file, | |
function: function, | |
line: line, | |
timeoutAfter: timeoutAfter) { (fn: @escaping (Result<Void, E>) -> Void) in | |
try closure(fn) | |
} | |
} | |
func wait(file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
timeoutAfter: TimeInterval? = DEFAULT_TIMEOUT, | |
for closure: @escaping (@escaping (Result<Void, Error>) -> Void) throws -> Void) throws { | |
try _wait( | |
file: file, | |
function: function, | |
line: line, | |
timeoutAfter: timeoutAfter) { (fn: @escaping (Result<Void, Error>) -> Void) in | |
try closure(fn) | |
} | |
} | |
extension Result where Success == Void { | |
init(error: Failure?) { | |
self.init((), error: error) | |
} | |
} | |
extension Result { | |
init(_ success: Success, error: Failure?) { | |
if let error = error { self = .failure(error) } | |
else { self = .success(success) } | |
} | |
} | |
// ----------- Awaits for func w/ callback syntax: ------------- // | |
// | |
// func doThing(<A,B,C...>, callback: (Result<MyReturnType, MyErrorType>) throws -> Void) | |
func await<T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (@escaping (Result<T, E>) -> Void) throws -> Void) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
try closure(fn) | |
} | |
} | |
func await<A, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, @escaping (Result<T, E>) -> Void) throws -> Void, _ a: A) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
try closure(a, fn) | |
} | |
} | |
func await<A, B, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, B, @escaping (Result<T, E>) -> Void) throws -> Void, | |
_ a: A, | |
_ b: B) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
try closure(a, b, fn) | |
} | |
} | |
func await<A, B, C, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, B, C, @escaping (Result<T, E>) -> Void) throws -> Void, | |
_ a: A, | |
_ b: B, | |
_ c: C) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
try closure(a, b, c, fn) | |
} | |
} | |
// ----------- Awaits for func w/ callback syntax: ------------- // | |
// | |
// func doThing(<A,B,C...>, callback: (Result<MyReturnType, MyErrorType>) -> Void) | |
func await<T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (@escaping (Result<T, E>) -> Void) -> Void) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
closure(fn) | |
} | |
} | |
func await<A, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, @escaping (Result<T, E>) -> Void) -> Void, | |
_ a: A) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
closure(a, fn) | |
} | |
} | |
func await<A, B, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, B, @escaping (Result<T, E>) -> Void) -> Void, | |
_ a: A, | |
_ b: B) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
closure(a, b, fn) | |
} | |
} | |
func await<A, B, C, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, B, C, @escaping (Result<T, E>) -> Void) -> Void, | |
_ a: A, | |
_ b: B, | |
_ c: C) throws -> T { | |
try wait(file: file, function: function, line: line) { fn in | |
closure(a, b, c, fn) | |
} | |
} | |
// ----------- Awaits for func w/ callback syntax: ------------- // | |
// | |
// func doThing(<A,B,C...>, callback: (MyErrorType?) -> Void) | |
func await<A, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, @escaping (E?) -> Void) -> Void, _ a: A) throws { | |
try wait(file: file, function: function, line: line) { (fn: @escaping (Result<Void, E>) -> Void) in | |
closure(a) { fn(Result(error: $0)) } | |
} | |
} | |
func await<A, B, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, B, @escaping (E?) -> Void) -> Void, | |
_ a: A, | |
_ b: B) throws { | |
try wait(file: file, function: function, line: line) { (fn: @escaping (Result<Void, E>) -> Void) in | |
closure(a, b) { fn(Result(error: $0)) } | |
} | |
} | |
// ----------- Awaits for func w/ callback syntax: ------------- // | |
// | |
// func doThing(<A,B,C...>, callback: (MyReturnType, Error?) -> Void) | |
func await<T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (@escaping (T, E?) -> Void) -> Void) throws -> T { | |
try wait(file: file, function: function, line: line) { (fn: @escaping (Result<T, E>) -> Void) in | |
closure { (a, e) in fn(Result(a, error: e)) } | |
} | |
} | |
func await<A, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, @escaping (T, E?) -> Void) -> Void, | |
_ a: A) throws -> T { | |
try wait(file: file, function: function, line: line) { (fn: @escaping (Result<T, E>) -> Void) in | |
closure(a) { (a, e) in fn(Result(a, error: e)) } | |
} | |
} | |
// ----------- Awaits for func w/ callback syntax: ------------- // | |
// | |
// func doThing(<A,B,C...>, callback: ((MyReturnType, Error?) -> Void)?) | |
@discardableResult | |
func await<A, T, E: Error>( | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line, | |
_ closure: @escaping (A, ((T, E?) -> Void)?) -> Void, | |
_ a: A) throws -> T { | |
try wait(file: file, function: function, line: line) { (fn: @escaping (Result<T, E>) -> Void) in | |
closure(a) { (a, e) in fn(Result(a, error: e)) } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment