Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions Sources/FoundationInternationalization/Locale/Locale_Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ struct LocaleCache : Sendable {
private var cachedCurrentNSLocale: _NSSwiftLocale!
private var cachedAutoupdatingNSLocale: _NSSwiftLocale!
private var cachedSystemNSLocale: _NSSwiftLocale!
private var cachedFixedNSLocales: [String : _NSSwiftLocale] = [:]
private var cachedFixedIdentifierToNSLocales: [String : _NSSwiftLocale] = [:]
private var cachedFixedLocaleToNSLocales: [_Locale : _NSSwiftLocale] = [:]
#endif

private var noteCount = -1
Expand Down Expand Up @@ -95,16 +96,28 @@ struct LocaleCache : Sendable {

#if FOUNDATION_FRAMEWORK
mutating func fixedNSLocale(_ id: String) -> _NSSwiftLocale {
if let locale = cachedFixedNSLocales[id] {
if let locale = cachedFixedIdentifierToNSLocales[id] {
return locale
} else {
let inner = Locale(inner: fixed(id))
let locale = _NSSwiftLocale(inner)
// We have found ObjC clients that rely upon an immortal lifetime for these `Locale`s, so we do not clear this cache.
cachedFixedNSLocales[id] = locale
cachedFixedIdentifierToNSLocales[id] = locale
return locale
}
}

mutating func fixedNSLocale(_ locale: _Locale) -> _NSSwiftLocale {
if let locale = cachedFixedLocaleToNSLocales[locale] {
return locale
} else {
let inner = Locale(inner: locale)
let nsLocale = _NSSwiftLocale(inner)
// We have found ObjC clients that rely upon an immortal lifetime for these `Locale`s, so we do not clear this cache.
cachedFixedLocaleToNSLocales[locale] = nsLocale
return nsLocale
}
}

mutating func currentNSLocale(preferences: LocalePreferences?, cache: Bool) -> _NSSwiftLocale? {
resetCurrentIfNeeded()
Expand Down Expand Up @@ -228,6 +241,10 @@ struct LocaleCache : Sendable {
lock.withLock { $0.fixedNSLocale(id) }
}

func fixedNSLocale(_ locale: _Locale) -> _NSSwiftLocale {
lock.withLock { $0.fixedNSLocale(locale) }
}

func autoupdatingCurrentNSLocale() -> _NSSwiftLocale {
lock.withLock { $0.autoupdatingNSLocale() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,10 @@ extension Locale : _ObjectiveCBridgeable {
@_semantics("convertToObjectiveC")
public func _bridgeToObjectiveC() -> NSLocale {
switch kind {
case .autoupdating, .fixed(_):
return _NSSwiftLocale(self)
case .autoupdating:
return LocaleCache.cache.autoupdatingCurrentNSLocale()
case .fixed(let l):
return LocaleCache.cache.fixedNSLocale(l)
case .bridged(let wrapper):
return wrapper._wrapped
}
Expand Down
40 changes: 40 additions & 0 deletions Tests/FoundationInternationalizationTests/LocaleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,47 @@ final class LocalBridgingTests : XCTestCase {
XCTAssertNotEqual(anyHashables[0], anyHashables[1])
XCTAssertEqual(anyHashables[1], anyHashables[2])
}

func test_autoupdatingBridge() {
let s1 = Locale.autoupdatingCurrent
let s2 = Locale.autoupdatingCurrent
let ns1 = s1 as NSLocale
let ns2 = s2 as NSLocale
// Verify that we don't create a new instance each time this is converted to NSLocale
XCTAssertTrue(ns1 === ns2)
}

func test_bridgingTwice() {
let s1 = NSLocale.system
let l1 = s1 as Locale
let s2 = NSLocale.system
let l2 = s2 as Locale
XCTAssertTrue(l1 as NSLocale === l2 as NSLocale)
}

func test_bridgingFixedTwice() {
let s1 = Locale(identifier: "en_US")
let ns1 = s1 as NSLocale
let s2 = Locale(identifier: "en_US")
let ns2 = s2 as NSLocale
XCTAssertTrue(ns1 === ns2)
}

func test_bridgingCurrentWithPrefs() {
// Verify that 'current with prefs' locales (which have identical identifiers but differing prefs) are correctly cached
let s1 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(metricUnits: true), disableBundleMatching: false)
let ns1 = s1 as NSLocale
let s2 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(metricUnits: true), disableBundleMatching: false)
let ns2 = s2 as NSLocale
let s3 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(measurementUnits: .centimeters), disableBundleMatching: false)
let ns3 = s3 as NSLocale

XCTAssertTrue(ns1 === ns2)
XCTAssertTrue(ns1 !== ns3)
XCTAssertTrue(ns2 !== ns3)
}
}

#endif // FOUNDATION_FRAMEWORK

// MARK: - FoundationPreview Disabled Tests
Expand Down