-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathModuleViewManager.swift
More file actions
92 lines (77 loc) · 3.68 KB
/
Copy pathModuleViewManager.swift
File metadata and controls
92 lines (77 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import Foundation
import Observation
import os
/// Менеджер UI-представления модуля. Реализует механизм обновления экрана:
///
/// - пересчитывает описание экрана (`view.body`), попутно записывая,
/// какие свойства вью-модели вью прочитала;
/// - при изменении любого из них ставит наблюдатель на фазу `beforeWaiting`
/// главного RunLoop — несколько изменений за один цикл схлопываются
/// в одну перерисовку;
/// - при каждом пересчёте записывает зависимости заново.
///
/// В монорепозитории это `AnyModuleViewManager` с `withPerceptionTracking`
/// (Perception — бэкпорт Observation на старые iOS). Здесь — нативный
/// `withObservationTracking`, механика та же.
///
/// Известные границы (в проде так же): изменение состояния с фонового
/// потока не будит спящий главный RunLoop — перерисовка случится
/// со следующим его пробуждением; изменение состояния прямо во время
/// пересчёта откладывает следующий пересчёт до следующего пробуждения.
@MainActor
public final class ModuleViewManager<View: ModuleView> {
public let view: View
private let viewBodyUpdateAction: @MainActor (View.Body) -> Void
private nonisolated let isUpdatingViewBody = OSAllocatedUnfairLock(initialState: false)
public init(
view: View,
onViewBodyUpdate: @escaping @MainActor (View.Body) -> Void
) {
self.view = view
viewBodyUpdateAction = onViewBodyUpdate
}
public func updateViewBodyWithObservation() {
withObservationTracking(updateViewBody) { [weak self] in
self?.updateViewBodyWithChanges()
}
}
private nonisolated func updateViewBodyWithChanges() {
let isAlreadyScheduled = isUpdatingViewBody.withLock { isUpdating in
defer { isUpdating = true }
return isUpdating
}
guard !isAlreadyScheduled else {
return
}
let viewRunLoopObserver = CFRunLoopObserverCreateWithHandler(
kCFAllocatorDefault,
CFRunLoopActivity.beforeWaiting.rawValue,
false,
0
) { [weak self] observer, _ in
CFRunLoopRemoveObserver(
CFRunLoopGetMain(),
observer,
.commonModes
)
// Наблюдаем исключительно за циклом выполнения главного потока
MainActor.assumeIsolated {
self?.updateViewBodyWithObservation()
}
}
CFRunLoopAddObserver(
CFRunLoopGetMain(),
viewRunLoopObserver,
.commonModes
)
}
private func updateViewBody() {
// Флаг снимается до пересчёта: изменение состояния во время
// пересчёта должно запланировать следующий цикл, а не потеряться.
// В монорепозитории этот случай решает очередь отложенных действий.
isUpdatingViewBody.withLock { isUpdating in
isUpdating = false
}
viewBodyUpdateAction(view.body)
}
}