Random Joe
Random Joe

Reputation: 69

How to use a thread that is NOT the Main thread, to wait until a class static Bool variable is true?

Swift Newbie: I'm using GCD via DispatchQueue.global(qos: .background).async{ code } to execute HealthKit queries, before executing each query I need to wait (implemented via while/sleep loop) until a Class static protectedDataEncrypted: Bool (which I use to denote if AppleHealth data is encrypted and unaccessible) is false, I want to ensure that the GCD will never use the Main (UI) thread to check/sleep the static protectedDataEncrypted: Bool, as that will freeze the app.

So far the approach I've used works, but I'm not 100% convinced that it won't deadlock if the GCD for some reason uses the Main Thread to check/sleep the static Bool, what would be a better approach than using sleep, as per my code below?

In AppDelegate: i have the following:

static var protectedDataEncrypted = false

    override func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) {
        AppDelegate.protectedDataEncrypted = false
    }

    override func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication) {
        AppDelegate.protectedDataEncrypted = true
    }

In a separate class with a method which is invoked by DispatchQueue.global(qos: .background).async{ code }, I have the following method called before executing a HealthKit query

    func waitTillUnencrypted(){

        while (AppDelegate.protectedDataEncrypted){
                    DispatchQueue.global(qos: .background).sync {
                        Thread.sleep(forTimeInterval: 2)
                    }
        }
    }

Note: using DispatchQueue.global(qos: .background).sync to invoke Thread.sleep seems to prevent the UI freezing, whereas when I only had the Thread.sleep sometimes it would freeze, if rapidly locking/unlocking the screen continuously.

So far it works, but I'm not convinced it will work 100% of the time.

Many thanks in advance.

Upvotes: 1

Views: 326

Answers (1)

Rob Napier
Rob Napier

Reputation: 299585

First, there's no need for your own protectedDataEncrypted. That's already available directly as UIApplication.shared.isProtectedDataAvailable.

What you want is a queue you can stop. So create a queue for processing things. If protected data is not available, suspend it.

let protectedQueue = DispatchQueue(label: "protected")
if !UIApplication.shared.isProtectedDataAvailable {
    protectedQueue.suspend()
}

Now, anything placed on that queue using protectedQueue.dispatchAsync will either run immediately if possible, or just be queued up if not.

Then you can turn on and off the queue like you're doing.

override func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) {
    protectedQueue.resume()
}

override func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication) {
    protectedQueue.suspend()
}

All that said, it is usually better to just build your operations to blindly try to perform themselves, and then handle errors if they fail, rather than checking whether you think it will be successful. There are race conditions where you might start an operation successfully, but data protection might kick in before you finish. You have to handle that case. Since you have to handle that case, you should generally just let that handler be how you handle not having access.

But in cases where preflight checking can be useful, or if it impacts user visible elements, then the above can be useful.

Polling with sleep is never the answer. Even if you wanted to poll (which you should avoid if at all possible), you should never use Thread.sleep. That ties up the entire thread and prevents anything else from using it. The way to poll, if you're forced to do so, is by rescheduling yourself with dispatchAfter.

Upvotes: 2

Related Questions