8
8
import UIKit
9
9
import Combine
10
10
11
- fileprivate func safetyAccessUI( _ closure: @escaping ( ) -> Void ) {
12
- if Thread . isMainThread {
13
- closure ( )
14
- } else {
15
- DispatchQueue . main. async {
16
- closure ( )
11
+ /// Safety Access UI.
12
+ ///
13
+ /// A scheduler that performs all work on the main queue, as soon as possible.
14
+ ///
15
+ /// If the caller is already running on the main queue when an action is
16
+ /// scheduled, it may be run synchronously. However, ordering between actions
17
+ /// will always be preserved.
18
+ fileprivate final class UIScheduler {
19
+ private static let dispatchSpecificKey = DispatchSpecificKey < UInt8 > ( )
20
+ private static let dispatchSpecificValue = UInt8 . max
21
+ private static var __once : ( ) = {
22
+ DispatchQueue . main. setSpecific ( key: UIScheduler . dispatchSpecificKey,
23
+ value: dispatchSpecificValue)
24
+ } ( )
25
+
26
+ private let queueLength : UnsafeMutablePointer < Int32 > = {
27
+ let memory = UnsafeMutablePointer< Int32> . allocate( capacity: 1 )
28
+ memory. initialize ( to: 0 )
29
+ return memory
30
+ } ( )
31
+
32
+ deinit {
33
+ queueLength. deinitialize ( count: 1 )
34
+ queueLength. deallocate ( )
35
+ }
36
+
37
+ init ( ) {
38
+ /// This call is to ensure the main queue has been setup appropriately
39
+ /// for `UIScheduler`. It is only called once during the application
40
+ /// lifetime, since Swift has a `dispatch_once` like mechanism to
41
+ /// lazily initialize global variables and static variables.
42
+ _ = UIScheduler . __once
43
+ }
44
+
45
+ /// Queues an action to be performed on main queue. If the action is called
46
+ /// on the main thread and no work is queued, no scheduling takes place and
47
+ /// the action is called instantly.
48
+ func schedule( _ action: @escaping ( ) -> Void ) {
49
+ let positionInQueue = enqueue ( )
50
+
51
+ // If we're already running on the main queue, and there isn't work
52
+ // already enqueued, we can skip scheduling and just execute directly.
53
+ if positionInQueue == 1 , DispatchQueue . getSpecific ( key: UIScheduler . dispatchSpecificKey) == UIScheduler . dispatchSpecificValue {
54
+ action ( )
55
+ dequeue ( )
56
+ } else {
57
+ DispatchQueue . main. async {
58
+ defer { self . dequeue ( ) }
59
+ action ( )
60
+ }
17
61
}
18
62
}
63
+
64
+ private func dequeue( ) {
65
+ OSAtomicDecrement32 ( queueLength)
66
+ }
67
+ private func enqueue( ) -> Int32 {
68
+ OSAtomicIncrement32 ( queueLength)
69
+ }
19
70
}
20
71
72
+
21
73
@available ( iOS 13 . 0 , * )
22
74
extension StackKitCompatible where Base: UIView {
23
75
@@ -58,7 +110,7 @@ extension StackKitCompatible where Base: UIView {
58
110
) -> Self
59
111
{
60
112
receive ( publisher: publisher, storeIn: & cancellables) { view, output in
61
- safetyAccessUI {
113
+ UIScheduler ( ) . schedule {
62
114
view. isHidden = output
63
115
}
64
116
}
@@ -72,27 +124,14 @@ extension StackKitCompatible where Base: UIView {
72
124
) -> Self
73
125
{
74
126
receive ( publisher: publisher, cancellable: & cancellable) { view, output in
75
- safetyAccessUI {
127
+ UIScheduler ( ) . schedule {
76
128
view. isHidden = output
77
129
}
78
130
}
79
131
return self
80
132
}
81
-
82
133
}
83
134
84
- /**
85
- 这里由于设计逻辑,会有个问题
86
-
87
- 系统提供的 receive(on: DispatchQueue.main) 虽然也可以
88
- ⚠️ 但是:DispatchQueue 是个调度器
89
- 任务添加后需要等到下一个 loop cycle 才会执行
90
- 这样就会导致一个问题:
91
- ❌ 在主线程中修改值,并触发 `container.setNeedsLayout()` 的时候,
92
- `setNeedsLayout` 会先执行,而 `publisher` 会将任务派发到下一个 loop cycle (也就是 setNeedsLayout 和 receive 先后执行的问题)
93
- 所以这里采用 `safetyAccessUI` 来处理线程问题
94
- */
95
-
96
135
@available ( iOS 13 . 0 , * )
97
136
extension StackKitCompatible where Base: UILabel {
98
137
@@ -103,7 +142,7 @@ extension StackKitCompatible where Base: UILabel {
103
142
) -> Self
104
143
{
105
144
receive ( publisher: publisher, storeIn: & cancellables) { view, output in
106
- safetyAccessUI {
145
+ UIScheduler ( ) . schedule {
107
146
view. text = output
108
147
}
109
148
}
@@ -116,7 +155,7 @@ extension StackKitCompatible where Base: UILabel {
116
155
) -> Self
117
156
{
118
157
receive ( publisher: publisher, cancellable: & cancellable) { view, output in
119
- safetyAccessUI {
158
+ UIScheduler ( ) . schedule {
120
159
view. text = output
121
160
}
122
161
}
@@ -130,7 +169,7 @@ extension StackKitCompatible where Base: UILabel {
130
169
) -> Self
131
170
{
132
171
receive ( publisher: publisher, storeIn: & cancellables) { view, output in
133
- safetyAccessUI {
172
+ UIScheduler ( ) . schedule {
134
173
view. text = output
135
174
}
136
175
}
@@ -143,7 +182,7 @@ extension StackKitCompatible where Base: UILabel {
143
182
) -> Self
144
183
{
145
184
receive ( publisher: publisher, cancellable: & cancellable) { view, output in
146
- safetyAccessUI {
185
+ UIScheduler ( ) . schedule {
147
186
view. text = output
148
187
}
149
188
}
@@ -156,7 +195,7 @@ extension StackKitCompatible where Base: UILabel {
156
195
) -> Self
157
196
{
158
197
receive ( publisher: publisher, storeIn: & cancellables) { view, output in
159
- safetyAccessUI {
198
+ UIScheduler ( ) . schedule {
160
199
view. attributedText = output
161
200
}
162
201
}
@@ -169,7 +208,7 @@ extension StackKitCompatible where Base: UILabel {
169
208
) -> Self
170
209
{
171
210
receive ( publisher: publisher, cancellable: & cancellable) { view, output in
172
- safetyAccessUI {
211
+ UIScheduler ( ) . schedule {
173
212
view. attributedText = output
174
213
}
175
214
}
@@ -182,7 +221,7 @@ extension StackKitCompatible where Base: UILabel {
182
221
) -> Self
183
222
{
184
223
receive ( publisher: publisher, storeIn: & cancellables) { view, output in
185
- safetyAccessUI {
224
+ UIScheduler ( ) . schedule {
186
225
view. attributedText = output
187
226
}
188
227
}
@@ -195,7 +234,7 @@ extension StackKitCompatible where Base: UILabel {
195
234
) -> Self
196
235
{
197
236
receive ( publisher: publisher, cancellable: & cancellable) { view, output in
198
- safetyAccessUI {
237
+ UIScheduler ( ) . schedule {
199
238
view. attributedText = output
200
239
}
201
240
}
0 commit comments