0

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

EDIT: for those confused about MainActor, this is a useful read: https://www.swiftbysundell.com/articles/the-main-actor-attribute/

I can just wrap UIApplication.shared.open(settingsURL) in await MainActor.run but I'm still confused why MainActor doesn't work here / why Xcode thinks the wrong method is being called.

shim
  • 9,289
  • 12
  • 69
  • 108
  • `URL(string: UIApplication.openSettingsURLString)` is not an `async function` (it's just an optional), so there is no need to have `await` before it. – workingdog support Ukraine Feb 11 '22 at 23:42
  • The await there is because of the UIApplication. Won't compile without it. Doesn't affect the question either way. Could be any URL. – shim Feb 11 '22 at 23:44
  • Possibly relevant thread: https://twitter.com/dgregor79/status/1458656417825505284 Also https://stackoverflow.com/questions/70065630/swift-task-is-not-running-on-main-actor-as-expected – shim Feb 13 '22 at 05:57
  • "I can just wrap UIApplication.shared.open(settingsURL) in await MainActor.run but I'm still confused why MainActor doesn't work here / why Xcode thinks the wrong method is being called." Well, that's what my answer told you. It's a bug! But you kept rejecting that so I deleted it. – matt Feb 13 '22 at 23:02
  • Huh? I didn’t reject anything, what do you mean? – shim Feb 13 '22 at 23:05

1 Answers1

-1

The error indicates you may be 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?

EDIT-1: this also works for me.

struct ContentView: View {
    var body: some View {
        Text("testing")
            .onAppear {
                DispatchQueue.global(qos: .background).async {
                    Task {
                        guard let settingsURL = URL(string: "https://duckduckgo.com") else { return }
                        await UIApplication.shared.open(settingsURL)
                    }
                }
            }
    }
}
  • The thread the `Task` is run on shouldn't matter. [`MainActor`](https://developer.apple.com/documentation/swift/mainactor) is supposed to ensure that the calls to `UIApplication` are executed on the main thread. If I were running my task on the main thread there wouldn't be any issue in the first place, but moving execution to the main thread is not a solution. Note, downvote not mine. – shim Feb 13 '22 at 04:13
  • fair enough comment. However, in my code, I can wrap the `Task{..}` in a background task using `DispatchQueue.global(qos: .background).async { Task {..} }` and still work well for me. See updated code. – workingdog support Ukraine Feb 13 '22 at 05:45
  • I copied your new code and put a breakpoint on the `await` line, it still says `Thread.current` is the main thread. Maybe the system sees the only `await` call is a MainActor and thus the whole task is optimized at runtime to run on the main thread? Not sure yet. – shim Feb 13 '22 at 05:51
  • Change `Task` to `Task.detached` and you will see the issue in my question. – shim Feb 13 '22 at 05:55
  • ha yes, with `Task.detached` the issue is clear. – workingdog support Ukraine Feb 13 '22 at 06:47