imas145
imas145

Reputation: 1979

Returning value while dispatching to the main thread

I have a function func getValue() -> Bool that's called from a background thread. This is intentional and also required. Now, the getValue() needs to execute something on the main thread, in this case it needs to access UIApplication.shared.canOpenURL, which must be run on the main queue.

This is my current function:

func getValue() -> Bool {
    guard let url = URL(string: "someurl") else { return false }
    return UIApplication.shared.canOpenURL(url)
}

How can I convert that function to a thread safe one, namely to make sure it always runs on the main thread, without

I've tried this:

// This causes a deadlock, see https://stackoverflow.com/a/42484670/1531270
func getValue() -> Bool {
    var flag = false

    let group = DispatchGroup()
    group.enter()
    DispatchQueue.main.async {
        if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) {
            flag = true
        }
        group.leave()
    }
    group.wait()

    return flag
}

and this:

// This crashes with EXC_BREAKPOINT (SIGTRAP) dispatch_sync called on queue already owned by current thread
func getValue() -> Bool {
    return DispatchQueue.main.sync {
        guard let url = URL(string: "someurl") else { return false }
        return UIApplication.shared.canOpenURL(url)
    }
}

but neither of them works. Any ideas?

Upvotes: 13

Views: 9497

Answers (2)

matt
matt

Reputation: 534958

I can't reproduce any issue with your second example. You didn't show how you're calling getValue, so I made something up:

func getValue() -> Bool {
   return DispatchQueue.main.sync {
        guard let url = URL(string: "testing://testing") else { return false }
        return UIApplication.shared.canOpenURL(url)
   }
}
override func viewDidLoad() {
    super.viewDidLoad()
    DispatchQueue.global(qos:.background).async {
        let ok = self.getValue()
        print(ok) // false, the right answer
    }
}

There's no "crash", so I would just go with that. When I use the scheme testing: I get false, and when I change testing: to https:, I return true, so clearly the method call is working.

Upvotes: 9

sam-w
sam-w

Reputation: 7687

You're looking for a semaphore - try this:

DispatchQueue.global(qos: .background).async {
    var value: Bool? = nil
    let semaphore = DispatchSemaphore(value: 0)
    DispatchQueue.main.async {
        let alert = UIAlertController(title: "Choose one", message: "Take your time, I'll wait", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "true", style: .default, handler: { _ in
            value = true
            semaphore.signal()
        }))
        alert.addAction(UIAlertAction(title: "false", style: .default, handler: { _ in
            value = false
            semaphore.signal()
        }))
        self.present(alert, animated: true, completion: nil)
    }
    semaphore.wait()
    print("Choice: \(value!)")
}

Or to use your example from above:

func getValue() -> Bool {
    var flag = false
    let semaphore = DispatchSemaphore(value: 0)

    DispatchQueue.main.async {
        if let url = URL(string: "someurl"), UIApplication.shared.canOpenURL(url) {
            flag = true
            semaphore.signal()
        }
    }
    semaphore.wait()
    return flag
}

Upvotes: 12

Related Questions