diff --git a/Sources/FoundationEssentials/Calendar/Calendar_Cache.swift b/Sources/FoundationEssentials/Calendar/Calendar_Cache.swift index 81b342d6a..97de542c6 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar_Cache.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar_Cache.swift @@ -15,30 +15,26 @@ internal import _ForSwiftFoundation import CoreFoundation #endif -/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone. -struct CalendarCache : Sendable { - - // MARK: - Concrete Classes - - // _CalendarICU, if present - static func calendarICUClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _CalendarProtocol.Type? { #if FOUNDATION_FRAMEWORK && canImport(_FoundationICU) - if useGregorian && identifier == .gregorian { - return _CalendarGregorian.self - } else { - return _CalendarICU.self - } +internal func _calendarICUClass() -> _CalendarProtocol.Type? { + _CalendarICU.self +} #else - if useGregorian && identifier == .gregorian { - return _CalendarGregorian.self - } else if let name = _typeByName("FoundationInternationalization._CalendarICU"), let t = name as? _CalendarProtocol.Type { - return t - } else { - return nil - } +dynamic package func _calendarICUClass() -> _CalendarProtocol.Type? { + nil +} #endif + +func _calendarClass(identifier: Calendar.Identifier, useGregorian: Bool) -> _CalendarProtocol.Type? { + if useGregorian && identifier == .gregorian { + return _CalendarGregorian.self + } else { + return _calendarICUClass() } +} +/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone. +struct CalendarCache : Sendable { // MARK: - State struct State : Sendable { @@ -78,7 +74,7 @@ struct CalendarCache : Sendable { } else { let id = Locale.current._calendarIdentifier // If we cannot create the right kind of class, we fail immediately here - let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)! + let calendarClass = _calendarClass(identifier: id, useGregorian: true)! let calendar = calendarClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) currentCalendar = calendar return calendar @@ -101,7 +97,7 @@ struct CalendarCache : Sendable { return cached } else { // If we cannot create the right kind of class, we fail immediately here - let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)! + let calendarClass = _calendarClass(identifier: id, useGregorian: true)! let new = calendarClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) fixedCalendars[id] = new return new @@ -140,7 +136,7 @@ struct CalendarCache : Sendable { func fixed(identifier: Calendar.Identifier, locale: Locale?, timeZone: TimeZone?, firstWeekday: Int?, minimumDaysInFirstWeek: Int?, gregorianStartDate: Date?) -> any _CalendarProtocol { // Note: Only the ObjC NSCalendar initWithCoder supports gregorian start date values. For Swift it is always nil. // If we cannot create the right kind of class, we fail immediately here - let calendarClass = CalendarCache.calendarICUClass(identifier: identifier, useGregorian: true)! + let calendarClass = _calendarClass(identifier: identifier, useGregorian: true)! return calendarClass.init(identifier: identifier, timeZone: timeZone, locale: locale, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: gregorianStartDate) } diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 9bb8f0bbb..a6fbbecf3 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -118,9 +118,11 @@ public protocol _NSNumberInitializer { static func initialize(value: some BinaryInteger) -> Any } -private let _nsNumberInitializer: (any _NSNumberInitializer.Type)? = { +@_spi(SwiftCorelibsFoundation) +dynamic public func _nsNumberInitializer() -> (any _NSNumberInitializer.Type)? { + // TODO: return nil here after swift-corelibs-foundation begins dynamically replacing this function _typeByName("Foundation._FoundationNSNumberInitializer") as? any _NSNumberInitializer.Type -}() +} #endif func _writeFileAttributePrimitive(_ value: T, as type: U.Type) -> Any { @@ -131,7 +133,7 @@ func _writeFileAttributePrimitive(_ value: T NSNumber(value: UInt64(value)) } #else - if let ns = _nsNumberInitializer?.initialize(value: value) { + if let ns = _nsNumberInitializer()?.initialize(value: value) { return ns } else { return U(value) @@ -143,7 +145,7 @@ func _writeFileAttributePrimitive(_ value: Bool) -> Any { #if FOUNDATION_FRAMEWORK NSNumber(value: value) #else - if let ns = _nsNumberInitializer?.initialize(value: value) { + if let ns = _nsNumberInitializer()?.initialize(value: value) { return ns } else { return value diff --git a/Sources/FoundationEssentials/Locale/Locale_Cache.swift b/Sources/FoundationEssentials/Locale/Locale_Cache.swift index f890838cd..892795d76 100644 --- a/Sources/FoundationEssentials/Locale/Locale_Cache.swift +++ b/Sources/FoundationEssentials/Locale/Locale_Cache.swift @@ -19,23 +19,20 @@ internal import os internal import _FoundationCShims -/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons. -struct LocaleCache : Sendable { - // MARK: - Concrete Classes - - // _LocaleICU, if present. Otherwise we use _LocaleUnlocalized. The `Locale` initializers are not failable, so we just fall back to the unlocalized type when needed without failure. - static let localeICUClass: _LocaleProtocol.Type = { #if FOUNDATION_FRAMEWORK && canImport(_FoundationICU) - return _LocaleICU.self +// Here, we always have access to _LocaleICU +internal func _localeICUClass() -> _LocaleProtocol.Type { + _LocaleICU.self +} #else - if let name = _typeByName("FoundationInternationalization._LocaleICU"), let t = name as? _LocaleProtocol.Type { - return t - } else { - return _LocaleUnlocalized.self - } +dynamic package func _localeICUClass() -> _LocaleProtocol.Type { + // Return _LocaleUnlocalized if FoundationInternationalization isn't loaded. The `Locale` initializers are not failable, so we just fall back to the unlocalized type when needed without failure. + _LocaleUnlocalized.self +} #endif - }() +/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons. +struct LocaleCache : Sendable { // MARK: - State struct State { @@ -99,7 +96,7 @@ struct LocaleCache : Sendable { return nil } - let locale = LocaleCache.localeICUClass.init(name: nil, prefs: preferences, disableBundleMatching: disableBundleMatching) + let locale = _localeICUClass().init(name: nil, prefs: preferences, disableBundleMatching: disableBundleMatching) if cache { // It's possible this was an 'incomplete locale', in which case we will want to calculate it again later. self.cachedCurrentLocale = locale @@ -122,7 +119,7 @@ struct LocaleCache : Sendable { if let locale = cachedFixedLocales[id] { return locale } else { - let locale = LocaleCache.localeICUClass.init(identifier: id, prefs: nil) + let locale = _localeICUClass().init(identifier: id, prefs: nil) cachedFixedLocales[id] = locale return locale } @@ -217,7 +214,7 @@ struct LocaleCache : Sendable { if let l = cachedFixedComponentsLocales[comps] { return l } else { - let new = LocaleCache.localeICUClass.init(components: comps) + let new = _localeICUClass().init(components: comps) cachedFixedComponentsLocales[comps] = new return new @@ -229,7 +226,7 @@ struct LocaleCache : Sendable { return locale } - let locale = LocaleCache.localeICUClass.init(identifier: "", prefs: nil) + let locale = _localeICUClass().init(identifier: "", prefs: nil) cachedSystemLocale = locale return locale } @@ -415,13 +412,13 @@ struct LocaleCache : Sendable { var (prefs, _) = preferences() if let overrides { prefs.apply(overrides) } - let inner = LocaleCache.localeICUClass.init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching) + let inner = _localeICUClass().init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching) return Locale(inner: inner) } func localeWithPreferences(identifier: String, prefs: LocalePreferences?) -> Locale { if let prefs { - let inner = LocaleCache.localeICUClass.init(identifier: identifier, prefs: prefs) + let inner = _localeICUClass().init(identifier: identifier, prefs: prefs) return Locale(inner: inner) } else { return Locale(inner: LocaleCache.cache.fixed(identifier)) diff --git a/Sources/FoundationEssentials/TimeZone/TimeZone_Cache.swift b/Sources/FoundationEssentials/TimeZone/TimeZone_Cache.swift index 8ac200a4e..1243def34 100644 --- a/Sources/FoundationEssentials/TimeZone/TimeZone_Cache.swift +++ b/Sources/FoundationEssentials/TimeZone/TimeZone_Cache.swift @@ -31,37 +31,25 @@ internal import _ForSwiftFoundation internal import CoreFoundation_Private.CFNotificationCenter #endif -/// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc. -struct TimeZoneCache : Sendable { - - // MARK: - Concrete Classes - - // _TimeZoneICU, if present - static let timeZoneICUClass: _TimeZoneProtocol.Type? = { -#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU) - _TimeZoneICU.self -#else - if let name = _typeByName("FoundationInternationalization._TimeZoneICU"), let t = name as? _TimeZoneProtocol.Type { - return t - } else { - return nil - } -#endif - }() - - // _TimeZoneGMTICU or _TimeZoneGMT - static let timeZoneGMTClass: _TimeZoneProtocol.Type = { + #if FOUNDATION_FRAMEWORK && canImport(_FoundationICU) - _TimeZoneGMTICU.self +internal func _timeZoneICUClass() -> _TimeZoneProtocol.Type? { + _TimeZoneICU.self +} +internal func _timeZoneGMTClass() -> _TimeZoneProtocol.Type { + _TimeZoneGMTICU.self +} #else - if let name = _typeByName("FoundationInternationalization._TimeZoneGMTICU"), let t = name as? _TimeZoneProtocol.Type { - return t - } else { - return _TimeZoneGMT.self - } +dynamic package func _timeZoneICUClass() -> _TimeZoneProtocol.Type? { + nil +} +dynamic package func _timeZoneGMTClass() -> _TimeZoneProtocol.Type { + _TimeZoneGMT.self +} #endif - }() +/// Singleton which listens for notifications about preference changes for TimeZone and holds cached values for current, fixed time zones, etc. +struct TimeZoneCache : Sendable { // MARK: - State struct State { @@ -239,7 +227,7 @@ struct TimeZoneCache : Sendable { } else if let cached = fixedTimeZones[identifier] { return cached } else { - if let innerTz = TimeZoneCache.timeZoneICUClass?.init(identifier: identifier) { + if let innerTz = _timeZoneICUClass()?.init(identifier: identifier) { fixedTimeZones[identifier] = innerTz return innerTz } else { @@ -254,7 +242,7 @@ struct TimeZoneCache : Sendable { } else { // In order to avoid bloating a cache with weird time zones, only cache values that are 30min offsets (including 1hr offsets). let doCache = abs(offset) % 1800 == 0 - if let innerTz = TimeZoneCache.timeZoneGMTClass.init(secondsFromGMT: offset) { + if let innerTz = _timeZoneGMTClass().init(secondsFromGMT: offset) { if doCache { offsetTimeZones[offset] = innerTz } diff --git a/Sources/FoundationEssentials/URL/URLParser.swift b/Sources/FoundationEssentials/URL/URLParser.swift index 8a2276498..efea6b538 100644 --- a/Sources/FoundationEssentials/URL/URLParser.swift +++ b/Sources/FoundationEssentials/URL/URLParser.swift @@ -158,17 +158,18 @@ package protocol UIDNAHook { static func decode(_ host: some StringProtocol) -> String? } +#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU) +internal func _uidnaHook() -> UIDNAHook.Type? { + UIDNAHookICU.self +} +#else +dynamic package func _uidnaHook() -> UIDNAHook.Type? { + nil +} +#endif + internal struct RFC3986Parser: URLParserProtocol { static let kind: URLParserKind = .RFC3986 - static let uidnaHook: UIDNAHook.Type? = { - #if FOUNDATION_FRAMEWORK && !canImport(_FoundationICU) - nil - #elseif FOUNDATION_FRAMEWORK - UIDNAHookICU.self - #else - _typeByName("FoundationInternationalization.UIDNAHookICU") as? UIDNAHook.Type - #endif - }() // MARK: - Encoding @@ -233,7 +234,7 @@ internal struct RFC3986Parser: URLParserProtocol { } static func shouldPercentEncodeHost(_ host: some StringProtocol, forScheme scheme: (some StringProtocol)?) -> Bool { - guard uidnaHook != nil else { + guard _uidnaHook() != nil else { // Always percent-encode the host if we can't access UIDNA encoding functions return true } @@ -279,13 +280,13 @@ internal struct RFC3986Parser: URLParserProtocol { static func IDNAEncodeHost(_ host: (some StringProtocol)?) -> String? { guard let host else { return nil } guard !host.isEmpty else { return "" } - return uidnaHook?.encode(host) + return _uidnaHook()?.encode(host) } static func IDNADecodeHost(_ host: (some StringProtocol)?) -> String? { guard let host else { return nil } guard !host.isEmpty else { return "" } - guard let uidnaHook else { return String(host) } + guard let uidnaHook = _uidnaHook() else { return String(host) } return uidnaHook.decode(host) } diff --git a/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift b/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift index 026945d62..0f0b97386 100644 --- a/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift +++ b/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift @@ -26,6 +26,13 @@ import Darwin internal import _FoundationICU +#if !FOUNDATION_FRAMEWORK +@_dynamicReplacement(for: _calendarICUClass()) +private func _calendarICUClass_localized() -> _CalendarProtocol.Type? { + return _CalendarICU.self +} +#endif + internal final class _CalendarICU: _CalendarProtocol, @unchecked Sendable { let lock: LockedState let identifier: Calendar.Identifier diff --git a/Sources/FoundationInternationalization/Locale/Locale_ICU.swift b/Sources/FoundationInternationalization/Locale/Locale_ICU.swift index 0c3512548..d26fb71aa 100644 --- a/Sources/FoundationInternationalization/Locale/Locale_ICU.swift +++ b/Sources/FoundationInternationalization/Locale/Locale_ICU.swift @@ -27,6 +27,13 @@ internal import _FoundationICU import Glibc #endif +#if !FOUNDATION_FRAMEWORK +@_dynamicReplacement(for: _localeICUClass()) +private func _localeICUClass_localized() -> any _LocaleProtocol.Type { + return _LocaleICU.self +} +#endif + let MAX_ICU_NAME_SIZE: Int32 = 1024 internal final class _LocaleICU: _LocaleProtocol, Sendable { diff --git a/Sources/FoundationInternationalization/TimeZone/TimeZone_GMTICU.swift b/Sources/FoundationInternationalization/TimeZone/TimeZone_GMTICU.swift index c97505dd8..980e37b55 100644 --- a/Sources/FoundationInternationalization/TimeZone/TimeZone_GMTICU.swift +++ b/Sources/FoundationInternationalization/TimeZone/TimeZone_GMTICU.swift @@ -16,6 +16,13 @@ import FoundationEssentials internal import _FoundationICU +#if !FOUNDATION_FRAMEWORK +@_dynamicReplacement(for: _timeZoneGMTClass()) +private func _timeZoneGMTClass_localized() -> _TimeZoneProtocol.Type { + return _TimeZoneGMTICU.self +} +#endif + internal final class _TimeZoneGMTICU : _TimeZoneProtocol, @unchecked Sendable { let offset: Int let name: String diff --git a/Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift b/Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift index bc8edc8d1..8b63f0931 100644 --- a/Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift +++ b/Sources/FoundationInternationalization/TimeZone/TimeZone_ICU.swift @@ -25,6 +25,13 @@ import ucrt #if canImport(_FoundationICU) internal import _FoundationICU +#if !FOUNDATION_FRAMEWORK +@_dynamicReplacement(for: _timeZoneICUClass()) +private func _timeZoneICUClass_localized() -> _TimeZoneProtocol.Type? { + return _TimeZoneICU.self +} +#endif + internal final class _TimeZoneICU: _TimeZoneProtocol, Sendable { init?(secondsFromGMT: Int) { fatalError("Unexpected init") diff --git a/Sources/FoundationInternationalization/URLParser+ICU.swift b/Sources/FoundationInternationalization/URLParser+ICU.swift index e41cc84de..49f73f306 100644 --- a/Sources/FoundationInternationalization/URLParser+ICU.swift +++ b/Sources/FoundationInternationalization/URLParser+ICU.swift @@ -15,7 +15,14 @@ import FoundationEssentials internal import _FoundationICU -internal final class UIDNAHookICU: UIDNAHook { +#if !FOUNDATION_FRAMEWORK +@_dynamicReplacement(for: _uidnaHook()) +private func _uidnaHook_localized() -> UIDNAHook.Type? { + return UIDNAHookICU.self +} +#endif + +struct UIDNAHookICU: UIDNAHook { // `Sendable` notes: `UIDNA` from ICU is thread safe. struct UIDNAPointer : @unchecked Sendable { init(_ ptr: OpaquePointer?) { self.idnaTranscoder = ptr }