Skip to content

Commit c2bc79a

Browse files
committed
Use noncopyable State type for DateFormatter and NumberFormatter
1 parent 5cb2337 commit c2bc79a

File tree

2 files changed

+146
-138
lines changed

2 files changed

+146
-138
lines changed

Diff for: Sources/Foundation/DateFormatter.swift

+63-55
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@ open class DateFormatter : Formatter, @unchecked Sendable {
1818
super.init()
1919
}
2020

21-
// Consumes state
22-
private convenience init(state: State) {
21+
private convenience init(state: consuming sending State) {
2322
self.init()
24-
nonisolated(unsafe) let consumedState = state
25-
_lock.withLock { $0 = consumedState }
23+
24+
// work around issue that state needs to be reinitialized after consuming
25+
struct Wrapper : ~Copyable, @unchecked Sendable {
26+
var value: State? = nil
27+
}
28+
var w = Wrapper(value: consume state)
29+
30+
_lock.withLock {
31+
$0 = w.value.take()!
32+
}
2633
}
2734

2835
open override func copy(with zone: NSZone? = nil) -> Any {
@@ -37,56 +44,57 @@ open class DateFormatter : Formatter, @unchecked Sendable {
3744
super.init(coder: coder)
3845
}
3946

40-
final class State {
41-
private var _formatter : CFDateFormatter? = nil
47+
struct State : ~Copyable {
48+
class Box {
49+
var formatter: CFDateFormatter?
50+
init() {}
51+
}
52+
53+
private var _formatter = Box()
4254

43-
func copy(with zone: NSZone? = nil) -> State {
44-
let copied = State()
45-
46-
func __copy<T>(_ keyPath: ReferenceWritableKeyPath<State, T>) {
47-
copied[keyPath: keyPath] = self[keyPath: keyPath]
48-
}
49-
50-
__copy(\.formattingContext)
51-
__copy(\.dateStyle)
52-
__copy(\.timeStyle)
53-
__copy(\._locale)
54-
__copy(\.generatesCalendarDates)
55-
__copy(\._timeZone)
56-
__copy(\._calendar)
57-
__copy(\.isLenient)
58-
__copy(\._twoDigitStartDate)
59-
__copy(\._eraSymbols)
60-
__copy(\._monthSymbols)
61-
__copy(\._shortMonthSymbols)
62-
__copy(\._weekdaySymbols)
63-
__copy(\._shortWeekdaySymbols)
64-
__copy(\._amSymbol)
65-
__copy(\._pmSymbol)
66-
__copy(\._longEraSymbols)
67-
__copy(\._veryShortMonthSymbols)
68-
__copy(\._standaloneMonthSymbols)
69-
__copy(\._shortStandaloneMonthSymbols)
70-
__copy(\._veryShortStandaloneMonthSymbols)
71-
__copy(\._veryShortWeekdaySymbols)
72-
__copy(\._standaloneWeekdaySymbols)
73-
__copy(\._shortStandaloneWeekdaySymbols)
74-
__copy(\._veryShortStandaloneWeekdaySymbols)
75-
__copy(\._quarterSymbols)
76-
__copy(\._shortQuarterSymbols)
77-
__copy(\._standaloneQuarterSymbols)
78-
__copy(\._shortStandaloneQuarterSymbols)
79-
__copy(\._gregorianStartDate)
80-
__copy(\.doesRelativeDateFormatting)
55+
func copy(with zone: NSZone? = nil) -> sending State {
56+
var copied = State()
57+
58+
copied.formattingContext = formattingContext
59+
copied.dateStyle = dateStyle
60+
copied.timeStyle = timeStyle
61+
copied._locale = _locale
62+
copied.generatesCalendarDates = generatesCalendarDates
63+
copied._timeZone = _timeZone
64+
copied._calendar = _calendar
65+
copied.isLenient = isLenient
66+
copied._twoDigitStartDate = _twoDigitStartDate
67+
copied._eraSymbols = _eraSymbols
68+
copied._monthSymbols = _monthSymbols
69+
copied._shortMonthSymbols = _shortMonthSymbols
70+
copied._weekdaySymbols = _weekdaySymbols
71+
copied._shortWeekdaySymbols = _shortWeekdaySymbols
72+
copied._amSymbol = _amSymbol
73+
copied._pmSymbol = _pmSymbol
74+
copied._longEraSymbols = _longEraSymbols
75+
copied._veryShortMonthSymbols = _veryShortMonthSymbols
76+
copied._standaloneMonthSymbols = _standaloneMonthSymbols
77+
copied._shortStandaloneMonthSymbols = _shortStandaloneMonthSymbols
78+
copied._veryShortStandaloneMonthSymbols = _veryShortStandaloneMonthSymbols
79+
copied._veryShortWeekdaySymbols = _veryShortWeekdaySymbols
80+
copied._standaloneWeekdaySymbols = _standaloneWeekdaySymbols
81+
copied._shortStandaloneWeekdaySymbols = _shortStandaloneWeekdaySymbols
82+
copied._veryShortStandaloneWeekdaySymbols = _veryShortStandaloneWeekdaySymbols
83+
copied._quarterSymbols = _quarterSymbols
84+
copied._shortQuarterSymbols = _shortQuarterSymbols
85+
copied._standaloneQuarterSymbols = _standaloneQuarterSymbols
86+
copied._shortStandaloneQuarterSymbols = _shortStandaloneQuarterSymbols
87+
copied._gregorianStartDate = _gregorianStartDate
88+
copied.doesRelativeDateFormatting = doesRelativeDateFormatting
8189

8290
// The last is `_dateFormat` because setting `dateStyle` and `timeStyle` make it `nil`.
83-
__copy(\._dateFormat)
91+
copied._dateFormat = _dateFormat
8492

8593
return copied
8694
}
8795

8896
func formatter() -> CFDateFormatter {
89-
guard let obj = _formatter else {
97+
guard let obj = _formatter.formatter else {
9098
let dateStyle = CFDateFormatterStyle(rawValue: CFIndex(dateStyle.rawValue))!
9199
let timeStyle = CFDateFormatterStyle(rawValue: CFIndex(timeStyle.rawValue))!
92100

@@ -95,14 +103,14 @@ open class DateFormatter : Formatter, @unchecked Sendable {
95103
if let dateFormat = _dateFormat {
96104
CFDateFormatterSetFormat(obj, dateFormat._cfObject)
97105
}
98-
_formatter = obj
106+
_formatter.formatter = obj
99107
return obj
100108
}
101109
return obj
102110
}
103111

104-
private func _reset() {
105-
_formatter = nil
112+
private mutating func _reset() {
113+
_formatter.formatter = nil
106114
}
107115

108116
// MARK: -
@@ -143,7 +151,7 @@ open class DateFormatter : Formatter, @unchecked Sendable {
143151
_setFormatterAttribute(formatter, attributeName: kCFDateFormatterGregorianStartDate, value: _gregorianStartDate?._cfObject)
144152
}
145153

146-
internal final func _setFormatterAttribute(_ formatter: CFDateFormatter, attributeName: CFString, value: AnyObject?) {
154+
internal func _setFormatterAttribute(_ formatter: CFDateFormatter, attributeName: CFString, value: AnyObject?) {
147155
if let value = value {
148156
CFDateFormatterSetProperty(formatter, attributeName, value)
149157
}
@@ -174,7 +182,7 @@ open class DateFormatter : Formatter, @unchecked Sendable {
174182
}
175183
}
176184

177-
/*@NSCopying*/ internal var _locale: Locale? { willSet { _reset() } }
185+
internal var _locale: Locale? { willSet { _reset() } }
178186
var locale: Locale! {
179187
get {
180188
guard let locale = _locale else { return .current }
@@ -187,7 +195,7 @@ open class DateFormatter : Formatter, @unchecked Sendable {
187195

188196
var generatesCalendarDates = false { willSet { _reset() } }
189197

190-
/*@NSCopying*/ internal var _timeZone: TimeZone? { willSet { _reset() } }
198+
internal var _timeZone: TimeZone? { willSet { _reset() } }
191199
var timeZone: TimeZone! {
192200
get {
193201
guard let tz = _timeZone else {
@@ -203,7 +211,7 @@ open class DateFormatter : Formatter, @unchecked Sendable {
203211
}
204212
}
205213

206-
/*@NSCopying*/ internal var _calendar: Calendar! { willSet { _reset() } }
214+
internal var _calendar: Calendar! { willSet { _reset() } }
207215
var calendar: Calendar! {
208216
get {
209217
guard let calendar = _calendar else {
@@ -221,7 +229,7 @@ open class DateFormatter : Formatter, @unchecked Sendable {
221229

222230
var isLenient = false { willSet { _reset() } }
223231

224-
/*@NSCopying*/ internal var _twoDigitStartDate: Date? { willSet { _reset() } }
232+
internal var _twoDigitStartDate: Date? { willSet { _reset() } }
225233
var twoDigitStartDate: Date? {
226234
get {
227235
guard let startDate = _twoDigitStartDate else {
@@ -234,7 +242,7 @@ open class DateFormatter : Formatter, @unchecked Sendable {
234242
}
235243
}
236244

237-
/*@NSCopying*/ var defaultDate: Date? { willSet { _reset() } }
245+
var defaultDate: Date? { willSet { _reset() } }
238246

239247
internal var _eraSymbols: [String]? { willSet { _reset() } }
240248
var eraSymbols: [String] {

Diff for: Sources/Foundation/NumberFormatter.swift

+83-83
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,18 @@ open class NumberFormatter : Formatter, @unchecked Sendable {
5353
super.init(coder: coder)
5454
}
5555

56-
// Consumes state
57-
private convenience init(state: State) {
56+
private convenience init(state: consuming sending State) {
5857
self.init()
59-
nonisolated(unsafe) let consumedState = state
60-
_lock.withLock { $0 = consumedState }
58+
59+
// work around issue that state needs to be reinitialized after consuming
60+
struct Wrapper : ~Copyable, @unchecked Sendable {
61+
var value: State? = nil
62+
}
63+
var w = Wrapper(value: consume state)
64+
65+
_lock.withLock {
66+
$0 = w.value.take()!
67+
}
6168
}
6269

6370
open override func copy(with zone: NSZone? = nil) -> Any {
@@ -74,98 +81,91 @@ open class NumberFormatter : Formatter, @unchecked Sendable {
7481
return numberFormatter.string(for: num)!
7582
}
7683

77-
final class State {
78-
private var _formatter: CFNumberFormatter? = nil
84+
struct State : ~Copyable {
85+
class Box {
86+
var formatter: CFNumberFormatter?
87+
init() {}
88+
}
89+
90+
private var _formatter = Box()
7991

8092
// MARK: -
8193

8294
func copy(with zone: NSZone? = nil) -> State {
83-
let copied = State()
84-
85-
func __copy<T>(_ keyPath: ReferenceWritableKeyPath<State, T>) {
86-
copied[keyPath: keyPath] = self[keyPath: keyPath]
87-
}
88-
89-
func __copy<T>(_ keyPath: ReferenceWritableKeyPath<State, T>) where T: NSCopying {
90-
copied[keyPath: keyPath] = self[keyPath: keyPath].copy(with: zone) as! T
91-
}
92-
93-
func __copy<T>(_ keyPath: ReferenceWritableKeyPath<State, T?>) where T: NSCopying {
94-
copied[keyPath: keyPath] = self[keyPath: keyPath]?.copy(with: zone) as! T?
95-
}
96-
97-
__copy(\.formattingContext)
98-
__copy(\._numberStyle)
99-
__copy(\._locale)
100-
__copy(\._generatesDecimalNumbers)
101-
__copy(\._textAttributesForNegativeValues)
102-
__copy(\._textAttributesForPositiveValues)
103-
__copy(\._allowsFloats)
104-
__copy(\._decimalSeparator)
105-
__copy(\._alwaysShowsDecimalSeparator)
106-
__copy(\._currencyDecimalSeparator)
107-
__copy(\._usesGroupingSeparator)
108-
__copy(\._groupingSeparator)
109-
__copy(\._zeroSymbol)
110-
__copy(\._textAttributesForZero)
111-
__copy(\._nilSymbol)
112-
__copy(\._textAttributesForNil)
113-
__copy(\._notANumberSymbol)
114-
__copy(\._textAttributesForNotANumber)
115-
__copy(\._positiveInfinitySymbol)
116-
__copy(\._textAttributesForPositiveInfinity)
117-
__copy(\._negativeInfinitySymbol)
118-
__copy(\._textAttributesForNegativeInfinity)
119-
__copy(\._positivePrefix)
120-
__copy(\._positiveSuffix)
121-
__copy(\._negativePrefix)
122-
__copy(\._negativeSuffix)
123-
__copy(\._currencyCode)
124-
__copy(\._currencySymbol)
125-
__copy(\._internationalCurrencySymbol)
126-
__copy(\._percentSymbol)
127-
__copy(\._perMillSymbol)
128-
__copy(\._minusSign)
129-
__copy(\._plusSign)
130-
__copy(\._exponentSymbol)
131-
__copy(\._groupingSize)
132-
__copy(\._secondaryGroupingSize)
133-
__copy(\._multiplier)
134-
__copy(\._formatWidth)
135-
__copy(\._paddingCharacter)
136-
__copy(\._paddingPosition)
137-
__copy(\._roundingMode)
138-
__copy(\._roundingIncrement)
139-
__copy(\._minimumIntegerDigits)
140-
__copy(\._maximumIntegerDigits)
141-
__copy(\._minimumFractionDigits)
142-
__copy(\._maximumFractionDigits)
143-
__copy(\._minimum)
144-
__copy(\._maximum)
145-
__copy(\._currencyGroupingSeparator)
146-
__copy(\._lenient)
147-
__copy(\._usesSignificantDigits)
148-
__copy(\._minimumSignificantDigits)
149-
__copy(\._maximumSignificantDigits)
150-
__copy(\._partialStringValidationEnabled)
151-
__copy(\._hasThousandSeparators)
152-
__copy(\._thousandSeparator)
153-
__copy(\._localizesFormat)
154-
__copy(\._positiveFormat)
155-
__copy(\._negativeFormat)
156-
__copy(\._roundingBehavior)
95+
var copied = State()
96+
97+
copied.formattingContext = formattingContext
98+
copied._numberStyle = _numberStyle
99+
copied._locale = _locale
100+
copied._generatesDecimalNumbers = _generatesDecimalNumbers
101+
copied._textAttributesForNegativeValues = _textAttributesForNegativeValues
102+
copied._textAttributesForPositiveValues = _textAttributesForPositiveValues
103+
copied._allowsFloats = _allowsFloats
104+
copied._decimalSeparator = _decimalSeparator
105+
copied._alwaysShowsDecimalSeparator = _alwaysShowsDecimalSeparator
106+
copied._currencyDecimalSeparator = _currencyDecimalSeparator
107+
copied._usesGroupingSeparator = _usesGroupingSeparator
108+
copied._groupingSeparator = _groupingSeparator
109+
copied._zeroSymbol = _zeroSymbol
110+
copied._textAttributesForZero = _textAttributesForZero
111+
copied._nilSymbol = _nilSymbol
112+
copied._textAttributesForNil = _textAttributesForNil
113+
copied._notANumberSymbol = _notANumberSymbol
114+
copied._textAttributesForNotANumber = _textAttributesForNotANumber
115+
copied._positiveInfinitySymbol = _positiveInfinitySymbol
116+
copied._textAttributesForPositiveInfinity = _textAttributesForPositiveInfinity
117+
copied._negativeInfinitySymbol = _negativeInfinitySymbol
118+
copied._textAttributesForNegativeInfinity = _textAttributesForNegativeInfinity
119+
copied._positivePrefix = _positivePrefix
120+
copied._positiveSuffix = _positiveSuffix
121+
copied._negativePrefix = _negativePrefix
122+
copied._negativeSuffix = _negativeSuffix
123+
copied._currencyCode = _currencyCode
124+
copied._currencySymbol = _currencySymbol
125+
copied._internationalCurrencySymbol = _internationalCurrencySymbol
126+
copied._percentSymbol = _percentSymbol
127+
copied._perMillSymbol = _perMillSymbol
128+
copied._minusSign = _minusSign
129+
copied._plusSign = _plusSign
130+
copied._exponentSymbol = _exponentSymbol
131+
copied._groupingSize = _groupingSize
132+
copied._secondaryGroupingSize = _secondaryGroupingSize
133+
copied._multiplier = _multiplier
134+
copied._formatWidth = _formatWidth
135+
copied._paddingCharacter = _paddingCharacter
136+
copied._paddingPosition = _paddingPosition
137+
copied._roundingMode = _roundingMode
138+
copied._roundingIncrement = _roundingIncrement
139+
copied._minimumIntegerDigits = _minimumIntegerDigits
140+
copied._maximumIntegerDigits = _maximumIntegerDigits
141+
copied._minimumFractionDigits = _minimumFractionDigits
142+
copied._maximumFractionDigits = _maximumFractionDigits
143+
copied._minimum = _minimum
144+
copied._maximum = _maximum
145+
copied._currencyGroupingSeparator = _currencyGroupingSeparator
146+
copied._lenient = _lenient
147+
copied._usesSignificantDigits = _usesSignificantDigits
148+
copied._minimumSignificantDigits = _minimumSignificantDigits
149+
copied._maximumSignificantDigits = _maximumSignificantDigits
150+
copied._partialStringValidationEnabled = _partialStringValidationEnabled
151+
copied._hasThousandSeparators = _hasThousandSeparators
152+
copied._thousandSeparator = _thousandSeparator
153+
copied._localizesFormat = _localizesFormat
154+
copied._positiveFormat = _positiveFormat
155+
copied._negativeFormat = _negativeFormat
156+
copied._roundingBehavior = _roundingBehavior
157157

158158
return copied
159159
}
160160

161161
// MARK: -
162162

163163
func _reset() {
164-
_formatter = nil
164+
_formatter.formatter = nil
165165
}
166166

167167
func formatter() -> CFNumberFormatter {
168-
if let obj = _formatter {
168+
if let obj = _formatter.formatter {
169169
return obj
170170
} else {
171171
let numberStyle = CFNumberFormatterStyle(rawValue: CFIndex(_numberStyle.rawValue))!
@@ -180,7 +180,7 @@ open class NumberFormatter : Formatter, @unchecked Sendable {
180180
}
181181
CFNumberFormatterSetFormat(obj, format._cfObject)
182182
}
183-
_formatter = obj
183+
_formatter.formatter = obj
184184
return obj
185185
}
186186
}

0 commit comments

Comments
 (0)