@@ -16,7 +16,7 @@ import CoreFoundation
16
16
#endif
17
17
18
18
/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
19
- struct CalendarCache : Sendable , ~ Copyable {
19
+ struct CalendarCache : Sendable {
20
20
21
21
// MARK: - Concrete Classes
22
22
@@ -38,71 +38,103 @@ struct CalendarCache : Sendable, ~Copyable {
38
38
}
39
39
#endif
40
40
}
41
+
42
+ // MARK: - State
41
43
42
- static let cache = CalendarCache ( )
43
-
44
- // The values stored in these two locks do not depend upon each other, so it is safe to access them with separate locks. This helps avoids contention on a single lock.
45
-
46
- private let _current = LockedState < ( any _CalendarProtocol ) ? > ( initialState: nil )
47
- private let _fixed = LockedState < [ Calendar . Identifier : any _CalendarProtocol ] > ( initialState: [ : ] )
48
-
49
- fileprivate init ( ) {
50
- }
51
-
52
- var current : any _CalendarProtocol {
53
- if let result = _current. withLock ( { $0 } ) {
54
- return result
44
+ struct State : Sendable {
45
+ // If nil, the calendar has been invalidated and will be created next time State.current() is called
46
+ private var currentCalendar : ( any _CalendarProtocol ) ?
47
+ private var autoupdatingCurrentCalendar : _CalendarAutoupdating ?
48
+ private var fixedCalendars : [ Calendar . Identifier : any _CalendarProtocol ] = [ : ]
49
+
50
+ private var noteCount = - 1
51
+ private var wasResetManually = false
52
+
53
+ mutating func check( ) {
54
+ #if FOUNDATION_FRAMEWORK
55
+ // On Darwin we listen for certain distributed notifications to reset the current Calendar.
56
+ let newNoteCount = _CFLocaleGetNoteCount ( ) + _CFTimeZoneGetNoteCount( ) + Int( _CFCalendarGetMidnightNoteCount ( ) )
57
+ #else
58
+ let newNoteCount = 1
59
+ #endif
60
+ if newNoteCount != noteCount || wasResetManually {
61
+ // rdar://102017659
62
+ // Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
63
+ // calendar. Creating the current calendar gets the current locale, decodes a plist
64
+ // from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
65
+ // leads to a deadlock if we are also initializing a class on the current thread
66
+ currentCalendar = nil
67
+ fixedCalendars = [ : ]
68
+
69
+ noteCount = newNoteCount
70
+ wasResetManually = false
71
+ }
72
+ }
73
+
74
+ mutating func current( ) -> any _CalendarProtocol {
75
+ check ( )
76
+ if let currentCalendar {
77
+ return currentCalendar
78
+ } else {
79
+ let id = Locale . current. _calendarIdentifier
80
+ // If we cannot create the right kind of class, we fail immediately here
81
+ let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
82
+ let calendar = calendarClass. init ( identifier: id, timeZone: nil , locale: Locale . current, firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
83
+ currentCalendar = calendar
84
+ return calendar
85
+ }
55
86
}
56
-
57
- let id = Locale . current. _calendarIdentifier
58
- // If we cannot create the right kind of class, we fail immediately here
59
- let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
60
- let calendar = calendarClass. init ( identifier: id, timeZone: nil , locale: Locale . current, firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
61
87
62
- return _current. withLock {
63
- if let current = $0 {
64
- // Someone beat us to setting it - use the existing one
65
- return current
88
+ mutating func autoupdatingCurrent( ) -> any _CalendarProtocol {
89
+ if let autoupdatingCurrentCalendar {
90
+ return autoupdatingCurrentCalendar
66
91
} else {
67
- $0 = calendar
92
+ let calendar = _CalendarAutoupdating ( )
93
+ autoupdatingCurrentCalendar = calendar
68
94
return calendar
69
95
}
70
96
}
97
+
98
+ mutating func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
99
+ check ( )
100
+ if let cached = fixedCalendars [ id] {
101
+ return cached
102
+ } else {
103
+ // If we cannot create the right kind of class, we fail immediately here
104
+ let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
105
+ let new = calendarClass. init ( identifier: id, timeZone: nil , locale: nil , firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
106
+ fixedCalendars [ id] = new
107
+ return new
108
+ }
109
+ }
110
+
111
+ mutating func reset( ) {
112
+ wasResetManually = true
113
+ }
71
114
}
72
-
115
+
116
+ let lock : LockedState < State >
117
+
118
+ static let cache = CalendarCache ( )
119
+
120
+ fileprivate init ( ) {
121
+ lock = LockedState ( initialState: State ( ) )
122
+ }
123
+
73
124
func reset( ) {
74
- // rdar://102017659
75
- // Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
76
- // calendar. Creating the current calendar gets the current locale, decodes a plist
77
- // from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
78
- // leads to a deadlock if we are also initializing a class on the current thread
79
- _current. withLock { $0 = nil }
80
- _fixed. withLock { $0 = [ : ] }
125
+ lock. withLock { $0. reset ( ) }
126
+ }
127
+
128
+ var current : any _CalendarProtocol {
129
+ lock. withLock { $0. current ( ) }
81
130
}
82
131
83
- // MARK: Singletons
84
-
85
- static let autoupdatingCurrent = _CalendarAutoupdating ( )
86
-
87
- // MARK: -
132
+ var autoupdatingCurrent : any _CalendarProtocol {
133
+ lock. withLock { $0. autoupdatingCurrent ( ) }
134
+ }
88
135
89
136
func fixed( _ id: Calendar . Identifier ) -> any _CalendarProtocol {
90
- if let existing = _fixed. withLock ( { $0 [ id] } ) {
91
- return existing
92
- }
93
-
94
- // If we cannot create the right kind of class, we fail immediately here
95
- let calendarClass = CalendarCache . calendarICUClass ( identifier: id, useGregorian: true ) !
96
- let new = calendarClass. init ( identifier: id, timeZone: nil , locale: nil , firstWeekday: nil , minimumDaysInFirstWeek: nil , gregorianStartDate: nil )
97
-
98
- return _fixed. withLock {
99
- if let existing = $0 [ id] {
100
- return existing
101
- } else {
102
- $0 [ id] = new
103
- return new
104
- }
105
- }
137
+ lock. withLock { $0. fixed ( id) }
106
138
}
107
139
108
140
func fixed( identifier: Calendar . Identifier , locale: Locale ? , timeZone: TimeZone ? , firstWeekday: Int ? , minimumDaysInFirstWeek: Int ? , gregorianStartDate: Date ? ) -> any _CalendarProtocol {
0 commit comments