I’ve written another Swift wrapper for GCD. Original C API does not fit Swift at all. It’s tedious and not very readable. Until recently I’ve been using this great gist by Tom Adriaenssen, but now I needed to use other QoS queues beside main and the background queue. Other default global queues available in GCD are: “Interactive”, “User Initiated” and “Utility”.

Problem with Tom’s implementation was that it declared three class-level methods for every queue it supported. This approach is untenable for more than two queues. Luckily with Swift we can extend types like dispatch_queue_t or dispatch_group_t and define methods that will reference self. This way we can implement sync, async, after methods that will be available on every queue including custom queues created by user.

Don’t worry about performance when compared with static definitions. I’ve declared the methods final giving the compiler hint that it can optimize and inline these calls. There won’t be any type checking or vtable lookups at runtime. So using dispatch.interactive.async {/*code*/} should generate the same binary as:

1
2
3
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
  /* code */
}

One problem I encountered was protection against deadlock when calling sync on the currently running queue. Tom had this small check only in the main queue sync implementation (background queue wasn’t protected):

1
2
3
4
5
6
7
8
class func main(block: dispatch_block_t) {
  if NSThread.isMainThread {
    block()
  }
  else {
    dispatch_sync(dispatch_get_main_queue(), block)
  }
}

This solution obviously works only for the main queue. But I needed to implement a method that will work with any queue. At first I thought about using dispatch_get_current_queue and comparing it with self, but it turns out that the method is deprecated in C and is not available in Swift at all. I decided I can tag queues using queue specific context and check the tag using dispatch_get_specific. I admit it’s a bit of a hack but it works for any queue. Since it adds a few extra cycles I’ve limited this to a separate method which I’ve called safeSync. If you don’t need this extra protection then stick to sync and you’ll get the same safety and performance as when calling dispatch_sync directly.

So finally here’s a usage overview:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// this will wait for completion and will deadlock when called from main queue
dispatch.main.sync {
  print("hello from main queue")
}

// this will return immediately
dispatch.utility.async {
  print("working with the Utility QoS")
}

// a safe sync operation that can be sent from any queue to any queue
dispatch.main.safeSync {
  print("safety in mind")
}

// this schedules a delayed block
dispatch.main.after(2.5) {
  printWithTime("hello from main after 2.5 seconds")
}

I’ve also thrown in support for grouping tasks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let group = dispatch.createGroup()

// schedule some asynchronous tasks
dispatch.interactive.async(group) {
  print("part of the job")
}
dispatch.user.async(group) {
  print("another part of the job")
}

// schedule asynchronous finalizer
group.notify {
  print("this will be executed when all of the group tasks complete")
}

// or synchronously wait for the group to complete
group.wait()

print("group completed")

Below is the entire implementation. It’s so small that there’s no point making it an external dependency. Just drop the code somewhere in your project and enjoy. You can also star this gist to get notified about any updates I make.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
//  dispatch.swift
//
//  Created by Adam Wróbel. Read more at:
//  http://adamwrobel.com/blog/2016/05/08/swift-gcd-wrapper/
//

import Foundation

internal extension dispatch_queue_t {
  /// Submits a block for asynchronous execution on this queue and returns
  /// immediately.
  final func async(block: dispatch_block_t) {
    dispatch_async(self, block)
  }

  /// Submits a block for asynchronous execution on this queue and associates
  /// the block with given group.
  final func async(group: dispatch_group_t, block: dispatch_block_t) {
    dispatch_group_async(group, self, block)
  }

  /// Submits a block object for execution on this queue and waits until that
  /// block completes.
  ///
  /// Calling this method on the current queue results in deadlock.
  final func sync(block: dispatch_block_t) {
    dispatch_sync(self, block)
  }

  /// Submits a block object for execution on this queue and waits until that
  /// block completes.
  ///
  /// Prevents deadlock by executing the block immediately if called on the
  /// current queue. Performing this safety check adds a little overhead when
  /// compared with `sync`.
  final func safeSync(block: dispatch_block_t) {
    isCurrent() ? block() : sync(block)
  }

  /// Enqueue a block for execution after specified number of seconds.
  final func after(seconds: Double, block: dispatch_block_t) {
    let when = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * 1_000_000_000))
    dispatch_after(when, self, block)
  }

  /// Asynchronously runs a block on this queue when the group's tasks are
  /// complete.
  final func after(group: dispatch_group_t, block: dispatch_block_t) {
    group.notify(self, block: block)
  }
}

internal extension dispatch_group_t {
  /// Waits until all of the group's tasks are complete.
  final func wait() {
    dispatch_group_wait(self, DISPATCH_TIME_FOREVER)
  }

  /// Asynchronously runs a block on the main queue when the group's tasks are
  /// complete.
  final func notify(block: dispatch_block_t) {
    dispatch_group_notify(self, dispatch.main, block)
  }

  /// Asynchronously runs a block on the given queue when the group's tasks are
  /// complete.
  final func notify(queue: dispatch_queue_t, block: dispatch_block_t) {
    dispatch_group_notify(self, queue, block)
  }
}

final internal class dispatch {
  /// Serial queue associated with the application’s main thread.
  class var main: dispatch_queue_t {
    return dispatch_get_main_queue()
  }

  /// Parallel queue used for work that is not user initiated or visible.
  class var bg: dispatch_queue_t {
    return dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
  }

  /// Parallel queue used for work which the user is unlikely to be immediately
  /// waiting for the results.
  class var utility: dispatch_queue_t {
    return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
  }

  /// Parallel queue used for work that has been explicitly requested by the
  /// user, and for which results must be immediately presented in order to
  /// allow for further user interaction.
  class var user: dispatch_queue_t {
    return dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
  }

  /// Parallel queue used for work directly involved in providing an
  /// interactive UI.
  class var interactive: dispatch_queue_t {
    return dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)
  }

  /// Creates a new group with which block objects can be associated.
  class func createGroup() -> dispatch_group_t {
    return dispatch_group_create()
  }
}

// Private extensions used exclusively to support `safeSync`

private extension dispatch {
  static var identityKey = UnsafePointer<Int8>(bitPattern: 1234)

  class var currentIdentity: UnsafeMutablePointer<Void> {
    return dispatch_get_specific(identityKey)
  }
}

private extension dispatch_queue_t {
  /// Uses queue specific context to determine if we're executing on this queue.
  func isCurrent() -> Bool {
    var ourIdentity = identity

    if ourIdentity == nil {
      ourIdentity = UnsafeMutablePointer<Void>(label)
      dispatch_queue_set_specific(self, dispatch.identityKey, ourIdentity, nil)
    }

    return ourIdentity == dispatch.currentIdentity
  }

  var label: UnsafePointer<Int8> {
    return dispatch_queue_get_label(self)
  }

  var identity: UnsafeMutablePointer<Void> {
    return dispatch_queue_get_specific(self, dispatch.identityKey)
  }
}

P.S. You’ll notice that I like to put all private methods in separate private extensions. This way you keep these methods away from the public interface and you don’t have to separately mark each of them as private.

P.S.S. I think the block parameter in dispatch_sync should be marked with noescape attribute, but GCD is a C API and noescape is only supported in Swift and Objective-C.