Skip to content

Commit 8c40aef

Browse files
authored
rdar://108022482 (Do more to cache Swift Locales bridged to Objective-C) (#29)
1 parent 0354dfb commit 8c40aef

File tree

3 files changed

+64
-5
lines changed

3 files changed

+64
-5
lines changed

Sources/FoundationInternationalization/Locale/Locale_Cache.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ struct LocaleCache : Sendable {
3333
private var cachedCurrentNSLocale: _NSSwiftLocale!
3434
private var cachedAutoupdatingNSLocale: _NSSwiftLocale!
3535
private var cachedSystemNSLocale: _NSSwiftLocale!
36-
private var cachedFixedNSLocales: [String : _NSSwiftLocale] = [:]
36+
private var cachedFixedIdentifierToNSLocales: [String : _NSSwiftLocale] = [:]
37+
private var cachedFixedLocaleToNSLocales: [_Locale : _NSSwiftLocale] = [:]
3738
#endif
3839

3940
private var noteCount = -1
@@ -95,16 +96,28 @@ struct LocaleCache : Sendable {
9596

9697
#if FOUNDATION_FRAMEWORK
9798
mutating func fixedNSLocale(_ id: String) -> _NSSwiftLocale {
98-
if let locale = cachedFixedNSLocales[id] {
99+
if let locale = cachedFixedIdentifierToNSLocales[id] {
99100
return locale
100101
} else {
101102
let inner = Locale(inner: fixed(id))
102103
let locale = _NSSwiftLocale(inner)
103104
// We have found ObjC clients that rely upon an immortal lifetime for these `Locale`s, so we do not clear this cache.
104-
cachedFixedNSLocales[id] = locale
105+
cachedFixedIdentifierToNSLocales[id] = locale
105106
return locale
106107
}
107108
}
109+
110+
mutating func fixedNSLocale(_ locale: _Locale) -> _NSSwiftLocale {
111+
if let locale = cachedFixedLocaleToNSLocales[locale] {
112+
return locale
113+
} else {
114+
let inner = Locale(inner: locale)
115+
let nsLocale = _NSSwiftLocale(inner)
116+
// We have found ObjC clients that rely upon an immortal lifetime for these `Locale`s, so we do not clear this cache.
117+
cachedFixedLocaleToNSLocales[locale] = nsLocale
118+
return nsLocale
119+
}
120+
}
108121

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

244+
func fixedNSLocale(_ locale: _Locale) -> _NSSwiftLocale {
245+
lock.withLock { $0.fixedNSLocale(locale) }
246+
}
247+
231248
func autoupdatingCurrentNSLocale() -> _NSSwiftLocale {
232249
lock.withLock { $0.autoupdatingNSLocale() }
233250
}

Sources/FoundationInternationalization/Locale/Locale_Wrappers.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,8 +673,10 @@ extension Locale : _ObjectiveCBridgeable {
673673
@_semantics("convertToObjectiveC")
674674
public func _bridgeToObjectiveC() -> NSLocale {
675675
switch kind {
676-
case .autoupdating, .fixed(_):
677-
return _NSSwiftLocale(self)
676+
case .autoupdating:
677+
return LocaleCache.cache.autoupdatingCurrentNSLocale()
678+
case .fixed(let l):
679+
return LocaleCache.cache.fixedNSLocale(l)
678680
case .bridged(let wrapper):
679681
return wrapper._wrapped
680682
}

Tests/FoundationInternationalizationTests/LocaleTests.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,47 @@ final class LocalBridgingTests : XCTestCase {
512512
XCTAssertNotEqual(anyHashables[0], anyHashables[1])
513513
XCTAssertEqual(anyHashables[1], anyHashables[2])
514514
}
515+
516+
func test_autoupdatingBridge() {
517+
let s1 = Locale.autoupdatingCurrent
518+
let s2 = Locale.autoupdatingCurrent
519+
let ns1 = s1 as NSLocale
520+
let ns2 = s2 as NSLocale
521+
// Verify that we don't create a new instance each time this is converted to NSLocale
522+
XCTAssertTrue(ns1 === ns2)
523+
}
524+
525+
func test_bridgingTwice() {
526+
let s1 = NSLocale.system
527+
let l1 = s1 as Locale
528+
let s2 = NSLocale.system
529+
let l2 = s2 as Locale
530+
XCTAssertTrue(l1 as NSLocale === l2 as NSLocale)
531+
}
532+
533+
func test_bridgingFixedTwice() {
534+
let s1 = Locale(identifier: "en_US")
535+
let ns1 = s1 as NSLocale
536+
let s2 = Locale(identifier: "en_US")
537+
let ns2 = s2 as NSLocale
538+
XCTAssertTrue(ns1 === ns2)
539+
}
540+
541+
func test_bridgingCurrentWithPrefs() {
542+
// Verify that 'current with prefs' locales (which have identical identifiers but differing prefs) are correctly cached
543+
let s1 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(metricUnits: true), disableBundleMatching: false)
544+
let ns1 = s1 as NSLocale
545+
let s2 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(metricUnits: true), disableBundleMatching: false)
546+
let ns2 = s2 as NSLocale
547+
let s3 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(measurementUnits: .centimeters), disableBundleMatching: false)
548+
let ns3 = s3 as NSLocale
549+
550+
XCTAssertTrue(ns1 === ns2)
551+
XCTAssertTrue(ns1 !== ns3)
552+
XCTAssertTrue(ns2 !== ns3)
553+
}
515554
}
555+
516556
#endif // FOUNDATION_FRAMEWORK
517557

518558
// MARK: - FoundationPreview Disabled Tests

0 commit comments

Comments
 (0)