Skip to content

Instantly share code, notes, and snippets.

@ccwasden
Created April 27, 2020 22:39
Show Gist options
  • Save ccwasden/2204f2045df38322a161cda3927cf135 to your computer and use it in GitHub Desktop.
Save ccwasden/2204f2045df38322a161cda3927cf135 to your computer and use it in GitHub Desktop.
Await+Threading
//
// 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