5
\$\begingroup\$

I created a basic background thread class in swift to replace the ugliness of:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
 // background thread code
 dispatch_async(dispatch_get_main_queue()) { () -> Void in
 // done, back to main thread
 }
}

with the expressiveness of:

Background { () -> () in
 // background code
 }.afterInterval(3) { () -> () in
 // main thread, run after interval IF not done
 }.completion { () -> () in
 // done, main thread
 }

afterInterval and completion are optional and interchangeable.

How reliable is this approach? Can we ever return from init(task) before completion is added, using the dot syntax? I have tested to my abilities and it seems rather bulletproof, but am wondering about the thoughts here.

class Background {
 private var done = false {
 didSet {
 if done {
 taskInterval = nil
 dispatch_async(dispatch_get_main_queue()) { () -> Void in
 completion?()
 }
 }
 }
 }
 private var taskInterval: (() -> ())?
 private var completion: (() -> ())?
 init(task: () -> ()) {
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
 task()
 self.done = true
 }
 }
 func afterInterval(interval: NSTimeInterval, closure: () -> ()) -> Background {
 taskInterval = closure
 let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
 dispatch_after(delayTime, dispatch_get_main_queue()) { [weak self] in
 self?.taskInterval?()
 }
 return self
 }
 func completion(closure: () -> ()) -> Background {
 completion = closure
 return self
 }
}

"SwiftyThreads" on Github

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Oct 22, 2015 at 20:49
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Yes, I see what you mean: there do appear to be dodgy race conditions. I do like where you are going with this, though – dot syntax and all. Still I found at least one failing test. If you paste your code in a playground and then add the following:

/* your code here */
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
var i = 0
Background {
 i += 1 // don't call `print` here!
}.afterInterval(0) {
 i += 10
}.completion {
 i += 100
}
let t = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC))
dispatch_after(t, dispatch_get_main_queue()) {
 print(i)
}
// prints: 101 (instead of 111)

True, delay of 0 seems a little contrived, but it does prove that your worries are founded. One way around this is to trigger a dispatch of those closures on demand (and repeatedly):

import Foundation
class Background {
 let qos: qos_class_t
 let task: (() -> ())?
 private(set) var delay: NSTimeInterval? = nil
 private(set) var delayedTask: (() -> ())? = nil
 private(set) var completionHandler: (() -> ())? = nil
 init(_ qos: qos_class_t = QOS_CLASS_BACKGROUND, task: (() -> ())? = nil) {
 self.qos = qos
 self.task = task
 }
 func delay(interval: NSTimeInterval, task: () -> ()) -> Background {
 self.delay = interval
 self.delayedTask = task
 return self
 }
 func completion(task: () -> ()) -> Background {
 self.completionHandler = task
 return self
 }
 func async() {
 dispatch_async(dispatch_get_global_queue(qos, 0)) { // TODO: unnecessary if `task == nil` 
 self.task?()
 if let delayedTask = self.delayedTask, let delay = self.delay {
 let t = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * NSTimeInterval(NSEC_PER_SEC)))
 dispatch_after(t, dispatch_get_main_queue(), delayedTask)
 }
 if let completionHandler = self.completionHandler {
 dispatch_async(dispatch_get_main_queue(), completionHandler)
 }
 }
 }
}

Which can be used like so:

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
var i = 0
let bg = Background {
 i += 1
}.delay(0) {
 i += 10
}.completion {
 i += 100
}
// nothing dispatched yet!
bg.async() // repeated invocations possible
bg.async()
Background().delay(1) {
 print(i)
}.async()
// prints: 222 (as it should)

In addition, I would probably rename Background...

answered Oct 23, 2015 at 11:36
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the reply, didn't think about testing with a delay of 0. Too focused on the completion block. Anyways, though this is an edge case, it confirms my suspicion that we could get race-y. Thanks again. \$\endgroup\$ Commented Oct 23, 2015 at 13:32

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.