I have a Swift package where the Package.swift file defines .defaultIsolation(MainActor.self) for my library target because it mostly contains view-level code. The Bundle.module that is automatically synthesized by SPM in resource_bundle_accessor.swift looks like this:
extension Foundation.Bundle {
/// Returns the resource bundle associated with the current Swift module.
static let module: Bundle = {
// Synthesized code here.
}()
}
There is no isolation specified and that means it’s usually nonisolated, which is what I want. But because my Package.swift file defines that the default isolation is MainActor, that Bundle.module is implicitly @MainActor. This is…not ideal.
Usually it’s no problem because all the view stuff is @MainActor anyway, but I also have custom error types that conform to Error via LocalizedError and Error is Sendable, turning my error types Sendable, too. So the problem shows when I’m using NSLocalizedString() for localization like this:
Main actor-isolated static property 'module' can not be referenced from a nonisolated context
I can’t think of anything I can do to fix this because I cannot affect the isolation of the generated Bundle.module and I cannot change my error type to be @MainActor because that (understandably) shows this error:
Conformance of 'MyError' to protocol 'LocalizedError' crosses into main actor-isolated code and can cause data races
If anyone knows a workaround, I’d be happy to hear it. But regardless, I think the Bundle.module property should be explicitly marked as nonisolated so it will work as expected in all situations, right? Is creating a GitHub issue for SPM the way to go here?
At first glance, annotating module with nonisolated should fix it. Annotating code with the nonisolated keyword allows you to opt out of the default @MainActor isolation.
Also, the module property from the sample, shouldn’t compile as it is currently of type () → Bundle. I assume that was just a typo here, but you would need to either convert it to a computed property or call it as an immediately invoked closure.
Exactly. The problem is that this Bundle.module property is defined in this resource_bundle_accessor.swift file that SPM generates. This is not editable and somewhere hidden, but you can get to it in Xcode by ⌘-clicking Bundle.module somewhere in your code.
But I guess I could just copy that code into some file and add my own version of .module And then probably mark it as deprecated with an upcoming Swift version.
// This is a version of `Bundle.module` from the synthesized `resource_bundle_accessor.swift` that works in `nonisolataed` code even if the Package.swift specifies `.defaultIsolation(MainActor.self)`.
import class Foundation.Bundle
import class Foundation.ProcessInfo
import struct Foundation.URL
private class BundleFinder {}
@available(swift, deprecated: 6.3, message: "If `Bundle.module` is now explicitly `nonisolated`, use it and remove this extension. If not, bump the Swift version in the deprecation.")
extension Foundation.Bundle {
/// Returns the resource bundle associated with the current Swift module.
nonisolated static let moduleNonisolated: Bundle = {
// Same as the synthesized file.
}()
}
I copied that from the generated resource_bundle_accessor.swift and left out the code that SPM generates. I’ll update the original post to be clearer about that.