I am calling await UIApplication.shared.open(settingsURL)
inside a Swift Task
but I am getting an Xcode runtime warning:
UIApplication.open(_:options:completionHandler:) must be used from the main thread only
Task {
guard let settingsURL = await URL(string: UIApplication.openSettingsURLString) else {
return
}
await UIApplication.shared.open(settingsURL) // <-- runtime warning when called
}
The SDK shows these methods:
@available(iOS 10.0, *)
open func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil)
@available(iOS 10.0, *)
open func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async -> Bool
The error message implies that it thinks I'm using the first, non-async one (because of the mention of the completion handler), but I'm not. UIApplication
is marked with MainActor
so my understanding is everything called via an instance of UIApplication
is run on the main thread automatically. This works just fine for other things I've called on UIApplication
elsewhere such as isRegisteredForRemoteNotifications
.
The app runs fine, no crashes or anything, just the warning from Xcode.
Is Xcode just confused here or am I actually doing something wrong?
Screenshot:
CodePudding user response:
The app runs fine, no crashes or anything, just the warning from Xcode
You probably haven't got the Main Thread Checker turned on:
Turn it on and run your code and you will crash.
I think this is probably a bug, maybe due to the complication of having a Swift double representation of this method which comes from Objective-C (Cocoa).
Unfortunately, though, whatever the reason, you are in fact running on a background thread even though you expect the context to be changed for you. You must not do that, so you will have to work around it. Change Task
to await MainActor.run
.
The reason I think it's a bug is that we can try to emulate the very same situation, and we don't get any issue.
I'll start with a MyObject Objective-C main actor class with an Objective-C method that takes a completion handler:
NS_SWIFT_UI_ACTOR
@interface MyObject : NSObject
- (void)doThis:(NSString*)what completionHandler:(void (^ __nullable)(BOOL success))completion;
@end
@implementation MyObject
- (void)doThis:(NSString*)what completionHandler:(void (^ __nullable)(BOOL success))completion {
completion(YES);
}
@end
Now I'll set up the same situation you're in: a Task called from a background thread, calling an async
wrapper of the completion handler method.
extension MyObject {
func doThis(_ what: String) async -> Bool {
return await withCheckedContinuation { continuation in
self.doThis(what) { ok in
continuation.resume(returning: ok)
}
}
}
}
let myObject = MyObject()
actor MyActor {
func callMe() async {
Task {
_ = await myObject.doThis("howdy")
print("done")
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
await MyActor().callMe()
}
}
}
The result is that we run in good order with no trouble from the Main Thread Checker. So I conclude that since we can do it, Cocoa should be able to do it.
CodePudding user response:
The error indicates you maybe using Task {...}
somewhere in your code that is not on the main thread.
With the following test code, I could not replicate your issue, using macos 12.3 Beta, Xcode 13.3,
targets ios 15 and macCatalyst 12. Tested on real devices, not Preview. It may be different on older systems.
This is the code I used in my tests:
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
Task {
guard let settingsURL = URL(string: "https://duckduckgo.com") else { return }
await UIApplication.shared.open(settingsURL) // <-- NO runtime warning when called
}
}
}
}
Does this code give you the same warning?