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.
Unfortunately in XCode 26.4, this seems to have a knock-on effect of breaking the use of string symbol generation in SwiftPM targets that use .defaultIsolation(MainActor.self).
This is because the generated string symbols use Foundation.Bundle.module if SWIFT_PACKAGE is defined:
// GeneratedStringSymbols_Localizable.swift
// Auto-Generated symbols for localized strings defined in āLocalizable.xcstringsā.
#if SWIFT_PACKAGE
private let resourceBundle = Foundation.Bundle.module
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
private nonisolated let resourceBundleDescription = LocalizedStringResource.BundleDescription.atURL(resourceBundle.bundleURL)
#else
private class ResourceBundleClass {}
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
private nonisolated let resourceBundleDescription = LocalizedStringResource.BundleDescription.forClass(ResourceBundleClass.self)
#endif
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
nonisolated extension LocalizedStringResource {
/**
Title for the awaiting-approval authentication debug scenario.
Localized string for key āauth.debug.scenario.awaiting-approval.titleā in table āLocalizable.xcstringsā.
*/
static var authDebugScenarioAwaitingApprovalTitle: LocalizedStringResource {
LocalizedStringResource("auth.debug.scenario.awaiting-approval.title", table: "Localizable", bundle: resourceBundleDescription)
}
XCode 26.4 changed this generated code to label resourceBundleDescription as nonisolated, but (a) itās not defining the private resourceBundle as nonisolated, and (b) even if it was, Bundle.module isnāt nonisolated.
The result is a compiler error, and there isnāt really a workaround that I can see.