From da3e595e5831646658cad5bd4734e76b47f5ef52 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 3 Jan 2026 20:15:15 +0800 Subject: [PATCH 1/2] Add UIKitUpdateCycle API --- .../COpenSwiftUI/Shims/UIKit/UIKit_Private.h | 44 ++++++++++++- .../Hosting/UIKit/UIKitUpdateCycle.swift | 64 +++++++++++++++++++ Sources/OpenSwiftUICore/Data/Update.swift | 2 +- 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift diff --git a/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h b/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h index ca4a4a0de..528ed1f4d 100644 --- a/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h +++ b/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h @@ -91,12 +91,52 @@ bool UIViewIgnoresTouchEvents(UIView *view); OPENSWIFTUI_EXPORT float UIAnimationDragCoefficient(void); +UIView * _UIKitCreateCustomView(Class class, CALayer *layer); + // MARK: - UIUpdate related private API from UIKitCore OPENSWIFTUI_EXPORT -bool _UIUpdateAdaptiveRateNeeded(); +bool _UIUpdateAdaptiveRateNeeded(void); -UIView * _UIKitCreateCustomView(Class class, CALayer *layer); +OPENSWIFTUI_EXPORT +bool _UIUpdateCycleEnabled(void); + +typedef struct _UIUpdateTiming { + uint64_t unknown1; + uint64_t unknown2; + uint64_t unknown3; +} _UIUpdateTiming; + +typedef void (^_UIUpdateSequenceCallback)(void * _Nullable context, CGFloat time, const _UIUpdateTiming * _Nonnull timing); + +typedef struct _UIUpdateSequenceItem _UIUpdateSequenceItem; + +typedef struct _UIUpdateSequence { + _UIUpdateSequenceItem * _Nullable first; +} _UIUpdateSequence; + +typedef struct _UIUpdateSequenceItem { + _UIUpdateSequenceItem * _Nullable next; + _UIUpdateSequence * _Nullable sequence; + const char * name; + uint32_t flags; + void * _Nullable context; + _UIUpdateSequenceCallback _Nullable callback; +} _UIUpdateSequenceItem; + +OPENSWIFTUI_EXPORT +_UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCATransactionCommitItem; + +OPENSWIFTUI_EXPORT +void * _Nonnull _UIUpdateSequenceInsertItem(_UIUpdateSequenceItem * _Nullable next, + _UIUpdateSequence * _Nullable sequence, + const char * name, + uint32_t flags, + void * _Nullable context, + _UIUpdateSequenceCallback _Nullable callback); + +OPENSWIFTUI_EXPORT +void _UIUpdateSequenceRemoveItem(_UIUpdateSequenceItem *item); OPENSWIFTUI_ASSUME_NONNULL_END diff --git a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift new file mode 100644 index 000000000..cce9c22ef --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift @@ -0,0 +1,64 @@ +// +// UIKitUpdateCycle.swift +// OpenSwiftUI +// +// Audited for 6.5.4 +// Status: WIP +// ID: 61722010453917A59567A84BEBF44765 (SwiftUI) + +#if os(iOS) || os(visionOS) +import COpenSwiftUI +@_spi(ForOpenSwiftUIOnly) +import OpenSwiftUICore + + +enum UIKitUpdateCycle { + private static var observerActions: [() -> Void] = [] + + private static var item: OpaquePointer? + + static var defaultUseSetNeedsLayout: Bool = { + let key = "UseSetNeedsLayoutForUpdates" + let defaults = UserDefaults.standard + guard defaults.object(forKey: key) != nil else { + return false + } + return defaults.bool(forKey: key) + }() + + static func addPreCommitObserver(_ action: @escaping () -> Void) { + guard _UIUpdateCycleEnabled() else { + return + } + if item == nil { + item = OpaquePointer( + _UIUpdateSequenceInsertItem( + _UIUpdateSequenceCATransactionCommitItem, + nil, + "OpenSwiftUIFlush", + 0, + nil, + ) { _, _, _ in + let actions = observerActions + guard !actions.isEmpty else { return } + observerActions = [] + for action in actions { + Update.perform(action) + } + }, + ) + + } + observerActions.append(action) + } + + static func addPreCommitObserverOrAsyncMain(_ action: @escaping () -> Void) { + if _UIUpdateCycleEnabled() { + addPreCommitObserver(action) + } else { + DispatchQueue.main.async(execute: action) + } + } +} + +#endif diff --git a/Sources/OpenSwiftUICore/Data/Update.swift b/Sources/OpenSwiftUICore/Data/Update.swift index bcd6319e8..96be18736 100644 --- a/Sources/OpenSwiftUICore/Data/Update.swift +++ b/Sources/OpenSwiftUICore/Data/Update.swift @@ -105,7 +105,7 @@ package enum Update { @inlinable @inline(__always) - static func perform(_ body: () throws -> T) rethrows -> T { + package static func perform(_ body: () throws -> T) rethrows -> T { begin() defer { end() } return try body() From 57bb397d062d4c9620ecf04e2e699d69c0aa4f09 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 3 Jan 2026 20:48:26 +0800 Subject: [PATCH 2/2] Add debugUIKitUpdateCycle API --- Example/HostingExample/ViewController.swift | 6 +++ .../COpenSwiftUI/Shims/UIKit/UIKit_Private.h | 37 ++++++++++++---- .../Hosting/UIKit/UIKitUpdateCycle.swift | 42 ++++++++++++++++--- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/Example/HostingExample/ViewController.swift b/Example/HostingExample/ViewController.swift index 68686d5f7..e83e46bc3 100644 --- a/Example/HostingExample/ViewController.swift +++ b/Example/HostingExample/ViewController.swift @@ -17,6 +17,8 @@ import UIKit import AppKit #endif +import OpenSwiftUI + #if os(iOS) || os(visionOS) class ViewController: UINavigationController { override func viewDidAppear(_ animated: Bool) { @@ -35,6 +37,10 @@ final class EntryViewController: UIViewController { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.pushHostingVC() } + + #if OPENSWIFTUI || DEBUG + // debugUIKitUpdateCycle() + #endif } @objc diff --git a/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h b/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h index 528ed1f4d..54f8d22f9 100644 --- a/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h +++ b/Sources/COpenSwiftUI/Shims/UIKit/UIKit_Private.h @@ -116,20 +116,43 @@ typedef struct _UIUpdateSequence { } _UIUpdateSequence; typedef struct _UIUpdateSequenceItem { - _UIUpdateSequenceItem * _Nullable next; - _UIUpdateSequence * _Nullable sequence; + const _UIUpdateSequenceItem * _Nullable next; + const _UIUpdateSequence * _Nullable sequence; const char * name; uint32_t flags; void * _Nullable context; - _UIUpdateSequenceCallback _Nullable callback; + void * _Nullable callback; // Actual type should be _UIUpdateSequenceCallback* } _UIUpdateSequenceItem; -OPENSWIFTUI_EXPORT -_UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCATransactionCommitItem; +// MARK: - UIUpdateSequence Items +// +// UIUpdateActionPhase defines specific phases of the UI update process. +// See: https://developer.apple.com/documentation/uikit/uiupdateactionphase +// +// Each UI update consists of several phases that run in a consistent order. +// There are two phase groups: standard and low-latency. +// +// Standard phase group (runs for each UI update): +// 1. beforeEventDispatch / afterEventDispatch -> HIDEventsItem +// 2. beforeCADisplayLinkDispatch / afterCADisplayLinkDispatch -> CADisplayLinksItem +// 3. beforeCATransactionCommit / afterCATransactionCommit -> CATransactionCommitItem +// +// Low-latency phase group (optional, runs after standard phases): +// 1. beforeLowLatencyEventDispatch / afterLowLatencyEventDispatch -> LowLatencyHIDEventsItem +// 2. beforeLowLatencyCATransactionCommit / afterLowLatencyCATransactionCommit -> LowLatencyCATransactionCommitItem + +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceScheduledItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceHIDEventsItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCADisplayLinksItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceAnimationsItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceCATransactionCommitItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceLowLatencyHIDEventsItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceLowLatencyCATransactionCommitItem; +OPENSWIFTUI_EXPORT const _UIUpdateSequenceItem * _Nonnull _UIUpdateSequenceDoneItem; OPENSWIFTUI_EXPORT -void * _Nonnull _UIUpdateSequenceInsertItem(_UIUpdateSequenceItem * _Nullable next, - _UIUpdateSequence * _Nullable sequence, +void * _Nonnull _UIUpdateSequenceInsertItem(const _UIUpdateSequenceItem * _Nullable next, + const _UIUpdateSequence * _Nullable sequence, const char * name, uint32_t flags, void * _Nullable context, diff --git a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift index cce9c22ef..214691366 100644 --- a/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift +++ b/Sources/OpenSwiftUI/Integration/Hosting/UIKit/UIKitUpdateCycle.swift @@ -11,13 +11,12 @@ import COpenSwiftUI @_spi(ForOpenSwiftUIOnly) import OpenSwiftUICore - -enum UIKitUpdateCycle { +package enum UIKitUpdateCycle { private static var observerActions: [() -> Void] = [] private static var item: OpaquePointer? - static var defaultUseSetNeedsLayout: Bool = { + package static var defaultUseSetNeedsLayout: Bool = { let key = "UseSetNeedsLayoutForUpdates" let defaults = UserDefaults.standard guard defaults.object(forKey: key) != nil else { @@ -26,7 +25,7 @@ enum UIKitUpdateCycle { return defaults.bool(forKey: key) }() - static func addPreCommitObserver(_ action: @escaping () -> Void) { + package static func addPreCommitObserver(_ action: @escaping () -> Void) { guard _UIUpdateCycleEnabled() else { return } @@ -52,13 +51,46 @@ enum UIKitUpdateCycle { observerActions.append(action) } - static func addPreCommitObserverOrAsyncMain(_ action: @escaping () -> Void) { + package static func addPreCommitObserverOrAsyncMain(_ action: @escaping () -> Void) { if _UIUpdateCycleEnabled() { addPreCommitObserver(action) } else { DispatchQueue.main.async(execute: action) } } + + #if DEBUG + private static func debugItem(_ item: UnsafePointer<_UIUpdateSequenceItem>) { + let name = String(cString: item.pointee.name) + _ = _UIUpdateSequenceInsertItem( + item, + nil, + ("OpenSwiftUIDebug" + name), + 0, + nil + ) { _, _, _ in + print("[UIKitUpdateCycle] \(name) phase") + } + } + + package static func setupDebug() { + guard _UIUpdateCycleEnabled() else { return } + debugItem(_UIUpdateSequenceScheduledItem) + debugItem(_UIUpdateSequenceHIDEventsItem) + debugItem(_UIUpdateSequenceCADisplayLinksItem) + debugItem(_UIUpdateSequenceAnimationsItem) + debugItem(_UIUpdateSequenceCATransactionCommitItem) + debugItem(_UIUpdateSequenceLowLatencyHIDEventsItem) + debugItem(_UIUpdateSequenceLowLatencyCATransactionCommitItem) + debugItem(_UIUpdateSequenceDoneItem) + } + #endif +} + +#if DEBUG +public func debugUIKitUpdateCycle() { + UIKitUpdateCycle.setupDebug() } +#endif #endif