thread/background task, xcode 7, swift 2

Hi


I need to have a background tack running. This task is to do some calculations about every five minutes and then update the UI. This can be either in a method or in a block of code. After doing a bunch of reading, my guess is this might be done with dispatch_async, but I haven't yet been able to find an example to get through those last few steps. Everything I've seen looks like dispatch_async is in a method call, which causes it to run asynchronously, but then the method goes away once complete. I'd like the method/bock of code to hang around for the life of the app (in the background) and update the UI irrespective of what the rest of the app is doing. Any examples of this would be appreciated. Thanks...

Accepted Reply

Do something like this:


func runevery(seconds: Double, closure: () -> ()) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))),
                   dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
        closure()
        runevery(seconds, closure: closure)
    }
}
runevery(5) {
    print("\n       Doing calculations\n")
    dispatch_async(dispatch_get_main_queue()) {
        print("\n       !!! UPDATING UI !!!\n")
    }
}


It is important that you do the calculations on the background thread (QOS_CLASS_BACKGROUND), and that you switch to the main thread before updating the UI. Otherwise, your UI will stop working during the calculations.

Replies

For mainly performance reasons (speed and battery), background tasks are only allowed in certain situations. Why do you need to do these calculations in the background? Can you not just do them when the user resumes? Do they involve communication with the outside world?

Thanks for your response ahltorp. My app is suppose to update data on the screen at regular intervals, I mention 5 minties, but it could be every 10 mins, 1/2 hr, etc., that hasn't been decide yet. It will complete its task in 24hrs, at which time, if the user desires, will start all over again. I though a background task would be the way to go, but if it can be done some other way, that would be good also. The user can interact with the app's UI while the background task is in operation. Thanks...

Ah, then it is perfectly fine. You can use dispatch_after to make it happen at a certain time, and then you just use dispatch_after in the end of the function that you call to schedule it again.


Remember to run this on a background queue, and then when you want to make changes, you have to do those changes on the main queue.

Thanks ahltorp, this is good to know. I've tried this and it does wait 5 seconds before running. What would be the way to run every 5 seconds?


Here's my delay func I have so far:

func delay(delay:Double, closure:()->()) {

dispatch_after(dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)

}



It is called from another spot:


delay(5) {

print("\n !!! UPDATING UI !!!\n")

}


I see the output


!!!! UPDATING UI !!!


in about 5 seconds and then not again. I would like to repeat the call every five seconds.


Thanks...

Do something like this:


func runevery(seconds: Double, closure: () -> ()) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))),
                   dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
        closure()
        runevery(seconds, closure: closure)
    }
}
runevery(5) {
    print("\n       Doing calculations\n")
    dispatch_async(dispatch_get_main_queue()) {
        print("\n       !!! UPDATING UI !!!\n")
    }
}


It is important that you do the calculations on the background thread (QOS_CLASS_BACKGROUND), and that you switch to the main thread before updating the UI. Otherwise, your UI will stop working during the calculations.

A repeating NSTimer is also suitable for periodic updates to an app's UI. For very frequent updates (many times a second) a CADisplayLink callback that does a setNeedsDisplay on the view can also be used for this.

A periodic timer is also a good way of doing this, but the block versions of NSTimer are not available pre-iOS 10 and macOS 10.12, and therefore not in Xcode 7. The old NSInvocation and selector versions are not very nice in Swift.

The old NSInvocation and selector versions are not very nice in Swift.

Well, the NSInvocation version is not very nice in any language (-:

OTOH, the selector version is reasonably nice to use from Swift 2, and definitely nicer that Swift 2’s GCD interface IMO.

Oh, and btw, if you do stick with GCD, consider using a timer source rather than

dispatch_after
. The latter was intended to be a convenience for one shot timers, whereas timer sources have built-in support for repeating.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

GCD would have to be involved anyway, since the question mentioned doing calculations in the background and then updating the UI, but of course then we could restrict it to just calling dispatch_async().


Here is a solution using a timer source, but is a bit messier than the dispatch_after solution. Also, the timer source has to be stored somewhere, since the timer stops if it is deallocated. The advantage is that the timer can be canceled.


func runevery(seconds: Double, closure: () -> ()) -> dispatch_source_t {
    let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0))

    dispatch_source_set_timer(timer, dispatch_walltime(nil, 0), UInt64(seconds * Double(NSEC_PER_SEC)), 10 * NSEC_PER_MSEC)
    dispatch_source_set_event_handler(timer, closure)
    dispatch_resume(timer)
    return timer
}


An NSTimer based solution:


class TimerReceiver {
    @objc func periodic() {
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
            print("\n       Doing calculations\n")
            dispatch_async(dispatch_get_main_queue()) {
                print("\n       !!! UPDATING UI !!!\n")
            }
        }
    }
}
let receiver = TimerReceiver()
let timer = NSTimer.scheduledTimerWithTimeInterval(5, target: receiver, selector: #selector(TimerReceiver.periodic), userInfo: nil, repeats: true)


Much nicer in Swift 3 and iOS 10/macOS 10.12:


let timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { (_) in
    DispatchQueue.global(qos: .background).async {
        print("\n       Doing calculations\n")
        DispatchQueue.main.async {
            print("\n       !!! UPDATING UI !!!\n")
        }
    }
}

Thatnks ahltorp, this works well 🙂