Syntactic sugar in Swift for asynchronous dispatches in Grand Central Dispatch

Related tags

Concurrency Async
Overview

Async

Carthage compatible CocoaPods compatible

Now more than syntactic sugar for asynchronous dispatches in Grand Central Dispatch (GCD) in Swift

Async sugar looks like this:

Async.userInitiated {
	10
}.background {
	"Score: \($0)"
}.main {
	label.text = $0
}

So even though GCD has nice-ish syntax as of Swift 3.0, compare the above with:

DispatchQueue.global(qos: .userInitiated).async {
	let value = 10
	DispatchQueue.global(qos: .background).async {
		let text = "Score: \(value)"
		DispatchQueue.main.async {
			label.text = text
		}
	}
}

AsyncGroup sugar looks like this:

let group = AsyncGroup()
group.background {
    print("This is run on the background queue")
}
group.background {
    print("This is also run on the background queue in parallel")
}
group.wait()
print("Both asynchronous blocks are complete")

Install

Swift Package Manager

Add To Your Project In Xcode 11:

File > Swift Packages > Add Package Dependency

Add As A Dependency In Package.swift:
dependencies: [
    .package(url: "https://github.com/duemunk/Async", from: "2.1.0"),
],

CocoaPods

use_frameworks!
pod "AsyncSwift"

Carthage

github "duemunk/Async"

Benefits

  1. Avoid code indentation by chaining
  2. Arguments and return types reduce polluted scopes

Things you can do

Supports the modern queue classes:

Async.main {}
Async.userInteractive {}
Async.userInitiated {}
Async.utility {}
Async.background {}

Chain as many blocks as you want:

Async.userInitiated {
	// 1
}.main {
	// 2
}.background {
	// 3
}.main {
	// 4
}

Store reference for later chaining:

let backgroundBlock = Async.background {
	print("This is run on the background queue")
}

// Run other code here...

// Chain to reference
backgroundBlock.main {
	print("This is run on the \(qos_class_self().description) (expected \(qos_class_main().description)), after the previous block")
}

Custom queues:

let customQueue = DispatchQueue(label: "CustomQueueLabel", attributes: [.concurrent])
let otherCustomQueue = DispatchQueue(label: "OtherCustomQueueLabel")
Async.custom(queue: customQueue) {
	print("Custom queue")
}.custom(queue: otherCustomQueue) {
	print("Other custom queue")
}

Dispatch block after delay:

let seconds = 0.5
Async.main(after: seconds) {
	print("Is called after 0.5 seconds")
}.background(after: 0.4) {
	print("At least 0.4 seconds after previous block, and 0.9 after Async code is called")
}

Cancel blocks that aren't already dispatched:

// Cancel blocks not yet dispatched
let block1 = Async.background {
	// Heavy work
	for i in 0...1000 {
		print("A \(i)")
	}
}
let block2 = block1.background {
	print("B – shouldn't be reached, since cancelled")
}
Async.main {
	// Cancel async to allow block1 to begin
	block1.cancel() // First block is _not_ cancelled
	block2.cancel() // Second block _is_ cancelled
}

Wait for block to finish – an ease way to continue on current queue after background task:

let block = Async.background {
	// Do stuff
}

// Do other stuff

block.wait()

How does it work

The way it work is by using the new notification API for GCD introduced in OS X 10.10 and iOS 8. Each chaining block is called when the previous queue has finished.

let previousBlock = {}
let chainingBlock = {}
let dispatchQueueForChainingBlock = ...

// Use the GCD API to extend the blocks
let _previousBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, previousBlock)
let _chainingBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, chainingBlock)

// Use the GCD API to call back when finishing the "previous" block
dispatch_block_notify(_previousBlock, dispatchQueueForChainingBlock, _chainingBlock)

The syntax part of the chaining works by having class methods on the Async object e.g. Async.main {} which returns a struct. The struct has matching methods e.g. theStruct.main {}.

Known bugs

Modern GCD queues don't work as expected in the iOS Simulator. See issues 13, 22.

Known improvements

The dispatch_block_t can't be extended. Workaround used: Wrap dispatch_block_t in a struct that takes the block as a property.

Apply

There is also a wrapper for dispatch_apply() for quick parallelisation of a for loop.

Apply.background(100) { i in
	// Do stuff e.g. print(i)
}

Note that this function returns after the block has been run all 100 times i.e. it is not asynchronous. For asynchronous behaviour, wrap it in a an Async block like Async.background { Apply.background(100) { ... } }.

AsyncGroup

AsyncGroup facilitates working with groups of asynchronous blocks.

Multiple dispatch blocks with GCD:

let group = AsyncGroup()
group.background {
    // Run on background queue
}
group.utility {
    // Run on utility queue, in parallel to the previous block
}
group.wait()

All modern queue classes:

group.main {}
group.userInteractive {}
group.userInitiated {}
group.utility {}
group.background {}

Custom queues:

let customQueue = dispatch_queue_create("Label", DISPATCH_QUEUE_CONCURRENT)
group.custom(queue: customQueue) {}

Wait for group to finish:

let group = AsyncGroup()
group.background {
    // Do stuff
}
group.background {
    // Do other stuff in parallel
}
// Wait for both to finish
group.wait()
// Do rest of stuff

Custom asynchronous operations:

let group = AsyncGroup()
group.enter()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    // Do stuff
    group.leave()
}
group.enter()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    // Do other stuff in parallel
    group.leave()
}
// Wait for both to finish
group.wait()
// Do rest of stuff

License

The MIT License (MIT)

Copyright (c) 2016 Tobias Due Munk

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Comments
  • CocoaPods Trunk

    CocoaPods Trunk

    I was wondering if you intended to push this library to Cocoapod's trunk. There is currently an Async library that would conflict with this one so a name change might be needed. This would allow users to use pod 'AsyncSwift' in their Podfile

    Guide to get setup on trunk

    Thanks for the amazing work :+1:

    help wanted question 
    opened by nanoxd 17
  • Travis CI not running tests

    Travis CI not running tests

    I can't make Travis CI work (spoiler: I haven't used it before). Check it our here.

    For every single test, the log states:

    Test did not run: the test bundle stopped running or crashed before the test suite started.
    

    The tests are only run for the OS X target.

    enhancement help wanted 
    opened by duemunk 16
  • Arguments and return values to blocks

    Arguments and return values to blocks

    Currently Async supports passing parameters through an external var like:

    var date: NSDate?
    Async.background {
        date = NSDate()
    }.main {
        // Access date 
    }
    

    Possible syntax for a better solution:

    Async.background {
        let date = NSDate()
    }.main(date) {
        // Access date
    }
    

    I have no idea if or how this is possible – please let me know if you do!

    enhancement help wanted question 
    opened by duemunk 15
  • AsyncSwift.podspec is outdated

    AsyncSwift.podspec is outdated

    When I try to install that framework using CocoaPods then 2.0.1 version will be installed. Any particular version is not specified in my Podfile. Furthermore, the latest version should contain the recent changes related to Swift 4 support.

    help wanted 
    opened by vldalx 14
  • Support for dispatch_group

    Support for dispatch_group

    Wait for multiple blocks to finish

    let block1 = Async.utility {}
    let block2 = Async.background {}
    Async.main(when: block1, block2) {}
    

    using dispatch_group()

    enhancement 
    opened by duemunk 12
  • Does not compile with Xcode 9 and Swift 4

    Does not compile with Xcode 9 and Swift 4

    let block = DispatchWorkItem(block: {
        reference.value = block()
    })
    
    // AsyncSwift/Sources/Async.swift:264:37: Missing argument for parameter #1 in call
    

    Side note: I also tried to convert the Async project with Xcode 9 + Swift 4 but we get a bunch of errors due to Void => () conversion which requires declaring closure params explicitly, very annoying. Not sure if that's a swift 4 beta bug or something that needs to be worked on.

    opened by ldiqual 11
  • Async for non-GCD async operations

    Async for non-GCD async operations

    It can be very useful if Async can use with async operations.

    Right now, I think Async can use only with sync operations.

    Async.background {
      // do some "sync" stuff 1
    }.background {
      // do some "sync" stuff 2 after sync stuff 1
    }
    

    But if I use some async operations such as Alamofire to upload files. Now the chain is just not wait for an upload to be finished.

    Async.background {
      // async upload
      Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
        .reponseJSON { (_, _, JSON, _) in
            // upload done!!
        }
    }.background {
      // do some stuff S after upload done!! ------>> Sorry S() is doing right away and not wait
      S()
    }
    

    An idea for accomplish this is something like passing a done block and waiting for done() to be called to continue to the next Async chain. I saw Quick/Nimble use this for waitUntil operation here.

    So, here is a use case proposal.

    Async.background { done in
      // async upload
      Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
        .reponseJSON { (_, _, JSON, _) in
            // upload done!!
            done()
        }
    }.background {
      // do some "sync" stuff S1 after upload done!!
      S1()
    }.background { done in
      // do some "async" stuff A1 after S1
      A1 { done() }
    }.main {
      // All uploading, S1 and A1 are done, updates UI here
    }
    

    The Async is now very smart and every operations (.background, .main, .utility, etc..) can take a parameterless block for sync operations and a "one parameter block" (that parameter is another block expose as parameter name done) for async operations.

    Any thought or possibility?

    opened by 0angelic0 10
  • Fix missing CFBundleVersion when built with Carthage (Reconfigured the whole project as Universal Target)

    Fix missing CFBundleVersion when built with Carthage (Reconfigured the whole project as Universal Target)

    Fixes #106

    See Carthage/Carthage#517 for details.

    The test target setting was causing unnecessary build for AsyncTest scheme.

    AsyncTest/Info.plist's CFBundleVersion is "$(CURRENT_PROJECT_VERSION)", which ended-up with empty string ("") because AsyncTest/Async target's CURRENT_PROJECT_VERSION is empty string ("").

    That means all carthage users had been using the wrong target. Scary...

    opened by toshi0383 9
  • Async.background reports/uses queues other than background

    Async.background reports/uses queues other than background

    I made a new Swift project (single view controller) and added Async.swift file. I used the following sample code in my viewDidLoad:

    Async.background {
        println("A: This is run on the \(qos_class_self().description) (expected \(QOS_CLASS_BACKGROUND.description))")
    }.main {
        println("B: This is run on the \(qos_class_self().description) (expected \(qos_class_main().description)), after the previous block")
    }
    

    Running the project multiple times, the results I get are either:

    A: This is run on the User Initiated (expected Background)
    B: This is run on the Main (expected Main), after the previous block
    

    Or:

    A: This is run on the Main (expected Background)
    B: This is run on the Main (expected Main), after the previous block
    

    Never do I get the first block reporting that it is running on the background queue.

    Xcode 6.1.1 (6A2008a)

    opened by damonjones 9
  • Adding dynamic framework target for Async

    Adding dynamic framework target for Async

    Adding a dynamic framework target for better linking into swift projects.

    Now that iOS8 supports dynamic frameworks, this is the ideal way to link in Swift projects. This is not yet supported by Cocoapods, however a PR for Swift frameworks exists.

    The following adds:

    • a new xCode project with framework target for iOS
    • exposes Async interface to framework
    • Adds iOS unit tests to the framework target tests (however these are currently broken both in the framework and the iOS app example)

    To dynamic link against the new framework, follow the instructions here, but reference to Async.framework instead

    opened by mowens 9
  • Implementing parameters and return between blocks

    Implementing parameters and return between blocks

    Implementing issue #3 .

    The Async is a generic struct with two types: In, Out.

    • The In is the parameter that it's blocks receive.
    • The Out is the parameter that it's return

    Chain blocks can receive the Out parameters from previous block or ignore it and return a new type if needed.

    All previous code should work without any change.

    Usage example:

    Async.background { () -> Int in
                return 10
            }.main { (i: Int) -> (String) in
                //Do something with i
                    return "Test"
            }.background { (s: String) -> (Double) in
                //Do something with s
                    return 10
            }.main { (d: Double) -> () in
                //Do something with d
            }
    

    The only problem is that we have to specify the types on the blocks because I didn't find how to constraint a method to not accept Void type as parameter.

    Problem example:

    Async.background { () -> Int in
                return 10
            }.main { (i) -> (String) in
                //Do something with i
                    return "Test"
            }
    

    Error because the compiler can not choose between type Int or Void to parameter i

    I'm waiting for your feedback and available to try fix anything that you find wrong.

    opened by BrunoMazzo 8
  • Crash build on Linux

    Crash build on Linux

    There is a problem when trying to build a project on Linux

    .build/checkouts/Async/Sources/Async/Async.swift:735:18: error: cannot find type 'qos_class_t' in scope
    public extension qos_class_t {
                     ^~~~~~~~~~~
    .build/checkouts/Async/Sources/Async/Async.swift:767:45: error: cannot find 'qos_class_main' in scope
            case DispatchQoS.QoSClass(rawValue: qos_class_main())!: result = .main
    
    opened by bordunosp 1
  • Lacking CocoaPods support

    Lacking CocoaPods support

    CocoaPods support has been missing for a long time (#29, #124, and more), and last time I gave it a shot, CocoaPods didn't play nice. I don't use it, so I'm considering just removing support for it completely.

    Any thoughts? cc @colemcampbell @Coledunsby

    help wanted 
    opened by duemunk 3
Releases(2.1.0)
Owner
Tobias Due Munk
Developer + designer + partner at Kabell & Munk
Tobias Due Munk
GCDTimer - Well tested Grand Central Dispatch (GCD) Timer in Swift

GCDTimer Well tested Grand Central Dispatch (GCD) Timer in Swift. Checkout the test file. Usage Long running timer import GCDTimer

Hemant Sapkota 183 Sep 9, 2022
Grand Central Dispatch simplified with swift.

GCDKit GCDKit is Grand Central Dispatch simplified with Swift. for Swift 1.2: Use version 1.0.1 for Swift 2.1 / 2.2: Use the master branch Introductio

John Estropia 317 Dec 6, 2022
A wrapper of Grand Central Dispatch written in Swift

GCD A wrapper of Grand Central Dispatch written in Swift. Examples gcd // submit your code for asynchronous execution on a global queue with high prio

Le Van Nghia 75 May 19, 2022
Chronos is a collection of useful Grand Central Dispatch utilities

Chronos is a collection of useful Grand Central Dispatch utilities. If you have any specific requests or ideas for new utilities, don't hesitate to create a new issue.

Comyar Zaheri 248 Sep 19, 2022
Queuer is a queue manager, built on top of OperationQueue and Dispatch (aka GCD).

Queuer is a queue manager, built on top of OperationQueue and Dispatch (aka GCD). It allows you to create any asynchronous and synchronous task easily, all managed by a queue, with just a few lines.

Fabrizio Brancati 1k Dec 2, 2022
Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift.

Futures Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift. It's lightweight, fast, and easy to understa

David Ask 60 Aug 11, 2022
GroupWork is an easy to use Swift framework that helps you orchestrate your concurrent, asynchronous functions in a clean and organized way

GroupWork is an easy to use Swift framework that helps you orchestrate your concurrent, asynchronous functions in a clean and organized way. This help

Quan Vo 42 Oct 5, 2022
AwaitKit is a powerful Swift library which provides a powerful way to write asynchronous code in a sequential manner.

AwaitKit is a powerful Swift library inspired by the Async/Await specification in ES8 (ECMAScript 2017) which provides a powerful way to write asynchronous code in a sequential manner.

Yannick Loriot 752 Dec 5, 2022
AsyncTimer is a precision asynchronous timer. You can also use it as a countdown timer

AsyncTimer ?? Features Can work as a countdown timer Can work as a periodic Timer Can work as a scheduled timer Working with user events (like: scroll

Adrian Bobrowski 26 Jan 1, 2023
A complete set of primitives for concurrency and reactive programming on Swift

A complete set of primitives for concurrency and reactive programming on Swift 1.4.0 is the latest and greatest, but only for Swift 4.2 and 5.0 use 1.

AsyncNinja 156 Aug 31, 2022
Hydra ⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

Lightweight full-featured Promises, Async & Await Library in Swift What's this? Hydra is full-featured lightweight library which allows you to write b

Daniele Margutti 2k Dec 24, 2022
Kommander is a Swift library to manage the task execution in different threads.

A lightweight, pure-Swift library for manage the task execution in different threads. Through the definition a simple but powerful concept, Kommand.

Intelygenz 173 Apr 11, 2022
SwiftCoroutine - Swift coroutines for iOS, macOS and Linux.

Many languages, such as Kotlin, Go, JavaScript, Python, Rust, C#, C++ and others, already have coroutines support that makes the async/await pattern i

Alex Belozierov 808 Dec 1, 2022
Venice - Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

Venice provides structured concurrency and CSP for Swift. Features Coroutines Coroutine cancelation Coroutine groups Channels Receive-only chan

Zewo 1.5k Dec 22, 2022
Elegant ⏱ interface for Swift apps

Each Elegant ⏱ interface for Swift apps Each is a NSTimer bridge library written in Swift. Features Requirements Installation Usage Leaks License Feat

Luca D'Alberti 764 Dec 29, 2022
Schedule timing task in Swift using a fluent API. (A friendly alternative to Timer)

Schedule(简体中文) Schedule is a timing tasks scheduler written in Swift. It allows you run timing tasks with elegant and intuitive syntax. Features Elega

Luo Xiu 1.8k Jan 7, 2023
Queues, timers, and task groups in Swift

Dispatcher eases the pain of using Grand Central Dispatch by introducing 4 new Swift classes. Dispatcher Queue Group Timer Requirements Swift 2.0+ Ins

Alec Larson 109 Jan 29, 2022
A Swift microframework for very easy atomic values.

Atomic Atomic is a fast, safe class for making values thread-safe in Swift. It is backed by pthread_mutex_lock which is the fastest, most-efficient lo

Adlai Holler 36 Sep 26, 2022
⏳ Collection of Swift 5.5 async/await utility functions.

⏳ FunAsync Collection of Swift 5.5 async/await utility functions. Throw <-> Result conversion asyncThrowsToAsyncResult asyncResultToAsyncThrows More C

Yasuhiro Inami 23 Oct 14, 2022