Reputation: 1979
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
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
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