Home > Net >  Xcode giving warning when using await UIApplication.shared.open(settingsURL) in Swift async function
Xcode giving warning when using await UIApplication.shared.open(settingsURL) in Swift async function

Time:02-12

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:

screenshot of error

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:

enter image description here

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?

  • Related