Skip to content

Commit f04794e

Browse files
authored
Add UIKitUpdateCycle API (#704)
1 parent c3a3efb commit f04794e

File tree

4 files changed

+168
-3
lines changed

4 files changed

+168
-3
lines changed

Example/HostingExample/ViewController.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import UIKit
1717
import AppKit
1818
#endif
1919

20+
import OpenSwiftUI
21+
2022
#if os(iOS) || os(visionOS)
2123
class ViewController: UINavigationController {
2224
override func viewDidAppear(_ animated: Bool) {
@@ -35,6 +37,10 @@ final class EntryViewController: UIViewController {
3537
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
3638
self.pushHostingVC()
3739
}
40+
41+
#if OPENSWIFTUI || DEBUG
42+
// debugUIKitUpdateCycle()
43+
#endif
3844
}
3945

4046
@objc

Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,75 @@ bool UIViewIgnoresTouchEvents(UIView *view);
9191
OPENSWIFTUI_EXPORT
9292
float UIAnimationDragCoefficient(void);
9393

94+
UIView * _UIKitCreateCustomView(Class class, CALayer *layer);
95+
9496
// MARK: - UIUpdate related private API from UIKitCore
9597

9698
OPENSWIFTUI_EXPORT
97-
bool _UIUpdateAdaptiveRateNeeded();
99+
bool _UIUpdateAdaptiveRateNeeded(void);
98100

99-
UIView * _UIKitCreateCustomView(Class class, CALayer *layer);
101+
OPENSWIFTUI_EXPORT
102+
bool _UIUpdateCycleEnabled(void);
103+
104+
typedef struct _UIUpdateTiming {
105+
uint64_t unknown1;
106+
uint64_t unknown2;
107+
uint64_t unknown3;
108+
} _UIUpdateTiming;
109+
110+
typedef void (^_UIUpdateSequenceCallback)(void * _Nullable context, CGFloat time, const _UIUpdateTiming * _Nonnull timing);
111+
112+
typedef struct _UIUpdateSequenceItem _UIUpdateSequenceItem;
113+
114+
typedef struct _UIUpdateSequence {
115+
_UIUpdateSequenceItem * _Nullable first;
116+
} _UIUpdateSequence;
117+
118+
typedef struct _UIUpdateSequenceItem {
119+
const _UIUpdateSequenceItem * _Nullable next;
120+
const _UIUpdateSequence * _Nullable sequence;
121+
const char * name;
122+
uint32_t flags;
123+
void * _Nullable context;
124+
void * _Nullable callback; // Actual type should be _UIUpdateSequenceCallback*
125+
} _UIUpdateSequenceItem;
126+
127+
// MARK: - UIUpdateSequence Items
128+
//
129+
// UIUpdateActionPhase defines specific phases of the UI update process.
130+
// See: https://developer.apple.com/documentation/uikit/uiupdateactionphase
131+
//
132+
// Each UI update consists of several phases that run in a consistent order.
133+
// There are two phase groups: standard and low-latency.
134+
//
135+
// Standard phase group (runs for each UI update):
136+
// 1. beforeEventDispatch / afterEventDispatch -> HIDEventsItem
137+
// 2. beforeCADisplayLinkDispatch / afterCADisplayLinkDispatch -> CADisplayLinksItem
138+
// 3. beforeCATransactionCommit / afterCATransactionCommit -> CATransactionCommitItem
139+
//
140+
// Low-latency phase group (optional, runs after standard phases):
141+
// 1. beforeLowLatencyEventDispatch / afterLowLatencyEventDispatch -> LowLatencyHIDEventsItem
142+
// 2. beforeLowLatencyCATransactionCommit / afterLowLatencyCATransactionCommit -> LowLatencyCATransactionCommitItem
143+
144+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceScheduledItem;
145+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceHIDEventsItem;
146+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCADisplayLinksItem;
147+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceAnimationsItem;
148+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCATransactionCommitItem;
149+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceLowLatencyHIDEventsItem;
150+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceLowLatencyCATransactionCommitItem;
151+
OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceDoneItem;
152+
153+
OPENSWIFTUI_EXPORT
154+
void * _Nonnull _UIUpdateSequenceInsertItem(const _UIUpdateSequenceItem * _Nullable next,
155+
const _UIUpdateSequence * _Nullable sequence,
156+
const char * name,
157+
uint32_t flags,
158+
void * _Nullable context,
159+
_UIUpdateSequenceCallback _Nullable callback);
160+
161+
OPENSWIFTUI_EXPORT
162+
void _UIUpdateSequenceRemoveItem(_UIUpdateSequenceItem *item);
100163

101164
OPENSWIFTUI_ASSUME_NONNULL_END
102165

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// UIKitUpdateCycle.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: WIP
7+
// ID: 61722010453917A59567A84BEBF44765 (SwiftUI)
8+
9+
#if os(iOS) || os(visionOS)
10+
import COpenSwiftUI
11+
@_spi(ForOpenSwiftUIOnly)
12+
import OpenSwiftUICore
13+
14+
package enum UIKitUpdateCycle {
15+
private static var observerActions: [() -> Void] = []
16+
17+
private static var item: OpaquePointer?
18+
19+
package static var defaultUseSetNeedsLayout: Bool = {
20+
let key = "UseSetNeedsLayoutForUpdates"
21+
let defaults = UserDefaults.standard
22+
guard defaults.object(forKey: key) != nil else {
23+
return false
24+
}
25+
return defaults.bool(forKey: key)
26+
}()
27+
28+
package static func addPreCommitObserver(_ action: @escaping () -> Void) {
29+
guard _UIUpdateCycleEnabled() else {
30+
return
31+
}
32+
if item == nil {
33+
item = OpaquePointer(
34+
_UIUpdateSequenceInsertItem(
35+
_UIUpdateSequenceCATransactionCommitItem,
36+
nil,
37+
"OpenSwiftUIFlush",
38+
0,
39+
nil,
40+
) { _, _, _ in
41+
let actions = observerActions
42+
guard !actions.isEmpty else { return }
43+
observerActions = []
44+
for action in actions {
45+
Update.perform(action)
46+
}
47+
},
48+
)
49+
50+
}
51+
observerActions.append(action)
52+
}
53+
54+
package static func addPreCommitObserverOrAsyncMain(_ action: @escaping () -> Void) {
55+
if _UIUpdateCycleEnabled() {
56+
addPreCommitObserver(action)
57+
} else {
58+
DispatchQueue.main.async(execute: action)
59+
}
60+
}
61+
62+
#if DEBUG
63+
private static func debugItem(_ item: UnsafePointer<_UIUpdateSequenceItem>) {
64+
let name = String(cString: item.pointee.name)
65+
_ = _UIUpdateSequenceInsertItem(
66+
item,
67+
nil,
68+
("OpenSwiftUIDebug" + name),
69+
0,
70+
nil
71+
) { _, _, _ in
72+
print("[UIKitUpdateCycle] \(name) phase")
73+
}
74+
}
75+
76+
package static func setupDebug() {
77+
guard _UIUpdateCycleEnabled() else { return }
78+
debugItem(_UIUpdateSequenceScheduledItem)
79+
debugItem(_UIUpdateSequenceHIDEventsItem)
80+
debugItem(_UIUpdateSequenceCADisplayLinksItem)
81+
debugItem(_UIUpdateSequenceAnimationsItem)
82+
debugItem(_UIUpdateSequenceCATransactionCommitItem)
83+
debugItem(_UIUpdateSequenceLowLatencyHIDEventsItem)
84+
debugItem(_UIUpdateSequenceLowLatencyCATransactionCommitItem)
85+
debugItem(_UIUpdateSequenceDoneItem)
86+
}
87+
#endif
88+
}
89+
90+
#if DEBUG
91+
public func debugUIKitUpdateCycle() {
92+
UIKitUpdateCycle.setupDebug()
93+
}
94+
#endif
95+
96+
#endif

Sources/OpenSwiftUICore/Data/Update.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ package enum Update {
105105

106106
@inlinable
107107
@inline(__always)
108-
static func perform<T>(_ body: () throws -> T) rethrows -> T {
108+
package static func perform<T>(_ body: () throws -> T) rethrows -> T {
109109
begin()
110110
defer { end() }
111111
return try body()

0 commit comments

Comments
 (0)