From d27dfba837d02d8fe900765d308b14397d38a78b Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 26 Jan 2026 02:34:38 +0800 Subject: [PATCH 1/2] Optimize OPENSWIFTUI_SUPPORT_2025_API code --- Sources/OpenSwiftUICore/Data/Combine/ObservedObject.swift | 2 +- Sources/OpenSwiftUICore/Data/Combine/StateObject.swift | 2 +- Sources/OpenSwiftUICore/View/DynamicView/DynamicView.swift | 2 +- .../OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift | 2 +- .../View/InterfaceIdiom/InterfaceIdiomPredicate.swift | 2 +- Sources/OpenSwiftUICore/View/View.swift | 3 +++ 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/OpenSwiftUICore/Data/Combine/ObservedObject.swift b/Sources/OpenSwiftUICore/Data/Combine/ObservedObject.swift index f14c353fd..ab0828364 100644 --- a/Sources/OpenSwiftUICore/Data/Combine/ObservedObject.swift +++ b/Sources/OpenSwiftUICore/Data/Combine/ObservedObject.swift @@ -211,7 +211,7 @@ public struct ObservedObject: DynamicProperty where ObjectType: Obse @available(OpenSwiftUI_v1_0, *) extension ObservedObject { - #if OPENSWIFTUI_RELEASE_2025 // Audited for 7.0.67 + #if OPENSWIFTUI_SUPPORT_2025_API // Audited for 7.0.67 nonisolated static func makeBoxAndSignal( in buffer: inout _DynamicPropertyBuffer, container: _GraphValue, diff --git a/Sources/OpenSwiftUICore/Data/Combine/StateObject.swift b/Sources/OpenSwiftUICore/Data/Combine/StateObject.swift index a576dc288..5612257e6 100644 --- a/Sources/OpenSwiftUICore/Data/Combine/StateObject.swift +++ b/Sources/OpenSwiftUICore/Data/Combine/StateObject.swift @@ -338,7 +338,7 @@ public struct StateObject: DynamicProperty where ObjectType: Observa inputs: inout _GraphInputs ) { var buf = _DynamicPropertyBuffer() - #if OPENSWIFTUI_RELEASE_2025 + #if OPENSWIFTUI_SUPPORT_2025_API let attribute = ObservedObject.makeBoxAndSignal(in: &buf, container: container, fieldOffset: 0) buffer.append(Box(links: buf, object: nil), fieldOffset: fieldOffset) addTreeValue( diff --git a/Sources/OpenSwiftUICore/View/DynamicView/DynamicView.swift b/Sources/OpenSwiftUICore/View/DynamicView/DynamicView.swift index 6cb3ac15b..c0485667f 100644 --- a/Sources/OpenSwiftUICore/View/DynamicView/DynamicView.swift +++ b/Sources/OpenSwiftUICore/View/DynamicView/DynamicView.swift @@ -12,7 +12,7 @@ package protocol DynamicView { static var canTransition: Bool { get } static var traitKeysDependOnView: Bool { get } associatedtype Metadata - associatedtype ID : Hashable + associatedtype ID: Hashable static func makeID() -> ID func childInfo(metadata: Metadata) -> (any Any.Type, ID?) func makeChildView(metadata: Metadata, view: Attribute, inputs: _ViewInputs) -> _ViewOutputs diff --git a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift index 56f118f93..b93e26161 100644 --- a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift +++ b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift @@ -6,7 +6,7 @@ // Status: Complete // ID: 39057DDA72E946BD17E1F42CCA55F7F6 (SwiftUICore) -#if OPENSWIFTUI_RELEASE_2024 +#if OPENSWIFTUI_SUPPORT_2024_API // MARK: - InterfaceIdiom diff --git a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift index c77fa2832..ca08c588a 100644 --- a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift +++ b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift @@ -5,7 +5,7 @@ // Audited for 6.0.87 // Status: Complete -#if OPENSWIFTUI_RELEASE_2024 +#if OPENSWIFTUI_SUPPORT_2024_API package struct InterfaceIdiomPredicate: ViewInputPredicate where Idiom: InterfaceIdiom { package init() {} diff --git a/Sources/OpenSwiftUICore/View/View.swift b/Sources/OpenSwiftUICore/View/View.swift index 920c61972..8586cd0bd 100644 --- a/Sources/OpenSwiftUICore/View/View.swift +++ b/Sources/OpenSwiftUICore/View/View.swift @@ -45,6 +45,9 @@ import OpenSwiftUI_SPI /// You can also collect groups of default modifiers into new, /// custom view modifiers for easy reuse. @available(OpenSwiftUI_v1_0, *) +#if OPENSWIFTUI_SUPPORT_2025_API && compiler(>=6.2) +@_typeEraser(DebugReplaceableView) +#endif @_typeEraser(AnyView) @preconcurrency @MainActor From 9f88ddc5ec3cc529ca58a513421f5c9c784f4cb1 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 26 Jan 2026 03:12:09 +0800 Subject: [PATCH 2/2] Add DebugReplaceableView API --- .../Modifier/CustomViewModifier.swift | 7 + .../DynamicView/DebugReplaceableView.swift | 196 ++++++++++++++++++ .../View/Input/CustomViewCountCache.swift | 98 +++++++++ .../OpenSwiftUICore/View/Input/ViewList.swift | 13 ++ 4 files changed, 314 insertions(+) create mode 100644 Sources/OpenSwiftUICore/View/DynamicView/DebugReplaceableView.swift create mode 100644 Sources/OpenSwiftUICore/View/Input/CustomViewCountCache.swift diff --git a/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift b/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift index 9af33628b..8b328d75c 100644 --- a/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift @@ -62,7 +62,12 @@ public struct PlaceholderContentView: View { } nonisolated public static func _viewListCount(inputs: _ViewListCountInputs) -> Int? { + #if OPENSWIFTUI_SUPPORT_2025_API + // TODO: inputs.cachedViewListCount(type: Self.self) + return nil + #else providerViewListCount(inputs: inputs) + #endif } public typealias Body = Never @@ -218,6 +223,7 @@ extension ViewModifierContentProvider { } } + #if !OPENSWIFTUI_SUPPORT_2025_API nonisolated fileprivate static func providerViewListCount( inputs: _ViewListCountInputs ) -> Int? { @@ -231,6 +237,7 @@ extension ViewModifierContentProvider { inputs.customModifierTypes.append(ObjectIdentifier(Self.self)) return input(inputs) } + #endif } extension _GraphInputs { diff --git a/Sources/OpenSwiftUICore/View/DynamicView/DebugReplaceableView.swift b/Sources/OpenSwiftUICore/View/DynamicView/DebugReplaceableView.swift new file mode 100644 index 000000000..7ae300881 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/DynamicView/DebugReplaceableView.swift @@ -0,0 +1,196 @@ +// +// DebugReplaceableView.swift +// OpenSwiftUICore +// +// Audited for 7.2.5 +// Status: Complete +// ID: 8F31ED3737048FE14E730EE04503F67D (SwiftUICore) + +#if OPENSWIFTUI_SUPPORT_2025_API +package import OpenAttributeGraphShims + +// MARK: - DebugReplaceableView + +/// Erases view opaque result types in debug builds. +/// +/// You don't use this type directly. Instead OpenSwiftUI creates this type on +/// your behalf when building in debug mode. +/// +/// When in debug builds, OpenSwiftUI will erase all opaque result types to +/// `DebugReplaceableView`. This allows developer tools, such as Xcode Previews, +/// to replace the definition of the view without fully rebuilding. +/// +/// As such, seeing this type in traces and the view debugger is expected when +/// building for debugging. This type erasure can impact performance, especially +/// when dealing with dynamic content (like content generated by a ForEach, or +/// List) so any performance testing should be done in release mode. +/// +/// This type acts similarly to an `AnyView` in that it type-erases its content, +/// however it depends on the underlying type of the value not actually changing +/// as it is in place of an opaque result type. As such, it *should not be used +/// in app code*. +@available(OpenSwiftUI_v7_0, *) +public struct DebugReplaceableView: View, PrimitiveView { + var storage: DebugReplaceableViewStorageBase + + /// Creates a debug replaceable view erasing the given view. + /// + /// You don't use this initializer directly. Instead OpenSwiftUI creates this + /// type on your behalf when building in debug mode. + /// + /// This type acts similarly to an `AnyView` in that it type-erases its + /// content, however it depends on the underlying type of the value not + /// actually changing as it is in place of an opaque result type. As such, + /// it *should not be used in app code*. + @available(*, deprecated, message: "DebugReplaceableView should not be used directly, to type erase a View, you should use AnyView.") + @_alwaysEmitIntoClient + public init(erasing view: V) where V: View { + self.init(_erasing: view) + } + + @usableFromInline + internal init(_erasing view: V) where V: View { + if let replaceableView = view as? DebugReplaceableView { + storage = replaceableView.storage + } else { + storage = DebugReplaceableViewStorage(view: view) + } + } + + nonisolated public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { + makeDynamicView(metadata: (), view: view, inputs: inputs) + } + + nonisolated public static func _makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs { + makeDynamicViewList(metadata: (), view: view, inputs: inputs) + } + + @available(OpenSwiftUI_v2_0, *) + nonisolated public static func _viewListCount(inputs: _ViewListCountInputs) -> Int? { + let info = inputs.debugReplaceableViewInfo + if info.countAsZero, let countedAsZero = info.countedAsZero { + countedAsZero.pointee = true + } + return info.countAsZero ? 0 : nil + } + + package func visitContent(_ visitor: inout Visitor) { + storage.visitContent(&visitor) + } + +} +@available(*, unavailable) +extension DebugReplaceableView: Sendable {} + +// MARK: - DebugReplaceableView: DynamicView + +extension DebugReplaceableView: DynamicView { + package static var canTransition: Bool { false } + + package func childInfo(metadata: ()) -> (any Any.Type, UniqueID?) { + (storage.childType, nil) + } + + package func makeChildView(metadata: (), view: Attribute, inputs: _ViewInputs) -> _ViewOutputs { + storage.makeChildView(view: view, inputs: inputs) + } + + package func makeChildViewList(metadata: (), view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs { + storage.makeChildViewList(view: view, inputs: inputs) + } +} + +// MARK: - DebugReplaceableViewStorageBase + +@available(OpenSwiftUI_v7_0, *) +@usableFromInline +internal class DebugReplaceableViewStorageBase { + fileprivate var childType: any Any.Type { + _openSwiftUIBaseClassAbstractMethod() + } + + fileprivate func makeChildView(view: Attribute, inputs: _ViewInputs) -> _ViewOutputs { + _openSwiftUIBaseClassAbstractMethod() + } + + fileprivate func makeChildViewList(view: Attribute, inputs: _ViewListInputs) -> _ViewListOutputs { + _openSwiftUIBaseClassAbstractMethod() + } + + fileprivate func visitContent(_ visitor: inout Visitor) where Visitor: ViewVisitor { + _openSwiftUIBaseClassAbstractMethod() + } +} + +@available(*, unavailable) +extension DebugReplaceableViewStorageBase: Sendable {} + +// MARK: - DebugReplaceableViewStorage + +private final class DebugReplaceableViewStorage: DebugReplaceableViewStorageBase where V: View { + let view: V + + init(view: V) { + self.view = view + super.init() + } + + override var childType: any Any.Type { + V.self + } + + override func makeChildView( + view: Attribute, + inputs: _ViewInputs + ) -> _ViewOutputs { + var inputs = inputs + inputs.base.pushStableType(V.self) + let childView = Attribute(DebugReplaceableViewChild(view: view)) + childView.value = self.view + return V.makeDebuggableView(view: _GraphValue(childView), inputs: inputs) + } + + override func makeChildViewList( + view: Attribute, + inputs: _ViewListInputs + ) -> _ViewListOutputs { + var inputs = inputs + inputs.base.pushStableType(V.self) + let childView = Attribute(DebugReplaceableViewChild(view: view)) + childView.value = self.view + return V.makeDebuggableViewList(view: _GraphValue(childView), inputs: inputs) + } + + override func visitContent(_ visitor: inout Visitor) where Visitor : ViewVisitor { + visitor.visit(view) + } +} + +// MARK: - DebugReplaceableViewInfo + +package struct DebugReplaceableViewInfo { + package var countAsZero: Bool + package var countedAsZero: UnsafeMutablePointer? + + package init(countAsZero: Bool, countedAsZero: UnsafeMutablePointer?) { + self.countAsZero = countAsZero + self.countedAsZero = countedAsZero + } +} + +// MARK: - DebugReplaceableViewChild + +fileprivate struct DebugReplaceableViewChild: StatefulRule, AsyncAttribute, CustomStringConvertible where V: View { + @Attribute var view: DebugReplaceableView + + typealias Value = V + + func updateValue() { + value = (view.storage as! DebugReplaceableViewStorage).view + } + + var description: String { + "\(V.self)" + } +} +#endif diff --git a/Sources/OpenSwiftUICore/View/Input/CustomViewCountCache.swift b/Sources/OpenSwiftUICore/View/Input/CustomViewCountCache.swift new file mode 100644 index 000000000..1101b6081 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Input/CustomViewCountCache.swift @@ -0,0 +1,98 @@ +// +// CustomViewCountCache.swift +// OpenSwiftUICore +// +// Audited for 7.2.5 +// Status: Complete +// ID: EB1EEB38755A34D46BFF2AE8785813E0 (SwiftUICore) + +#if OPENSWIFTUI_SUPPORT_2025_API +import OpenAttributeGraphShims + +/// A cache for tracking custom view counts during view list enumeration. +/// +/// This cache stores count information for custom views, allowing efficient +/// lookups during view list traversal without recomputing counts. +package struct CustomViewCountCache { + + /// Pointer to the head of a linked list of count entries + private var counts: UnsafeMutablePointer + + /// Optional modifier options that affect how counts are computed + private var modifierOptions: ModifierOptions? + + /// Updates the modifier options based on new inputs and a previous ID. + /// + /// This method synchronizes the cache's options with the current view list + /// inputs, ensuring that count computations remain consistent. + /// + /// - Parameters: + /// - inputs: The current view list count inputs + /// - previousID: The unique ID from the previous update + private mutating func updateOptions(inputs: _ViewListCountInputs, previousID: UniqueID) { + if let modifierOptions { + if modifierOptions.inputID == previousID { + self.modifierOptions = .init( + options: modifierOptions.options, + baseOptions: modifierOptions.baseOptions, + inputID: inputs.customInputs.id + ) + } + } else { + self.modifierOptions = .init( + options: inputs.options, + baseOptions: inputs.baseOptions, + inputID: inputs.customInputs.id + ) + } + } + + // MARK: - CustomViewCountCache.ModifierOptions + + /// Options that affect modifier behavior during count computation. + private struct ModifierOptions { + /// View list-specific options + let options: _ViewListInputs.Options + + /// Base graph input options + let baseOptions: _GraphInputs.Options + + /// Unique identifier for this set of inputs + var inputID: UniqueID + + init(options: _ViewListInputs.Options, baseOptions: _GraphInputs.Options, inputID: UniqueID) { + self.options = options + self.baseOptions = baseOptions + self.inputID = inputID + } + } + + // MARK: - CustomViewCountCache.Count + + /// A node in the linked list of cached counts for custom views. + private struct Count { + /// The type identifier for the cached view + var id: ObjectIdentifier + + /// The cached count value. + /// + /// This is a double-optional: + /// - `nil`: Not yet computed + /// - `.some(nil)`: Explicitly has no static count (dynamic) + /// - `.some(.some(n))`: Has a static count of n + var count: Int?? + + /// Pointer to the next count node in the linked list, or nil if this is the last node + let next: UnsafeMutablePointer? + + init(id: ObjectIdentifier, count: Int?? = nil, next: UnsafeMutablePointer? = nil) { + self.id = id + self.count = count + self.next = next + } + } +} + +@available(*, unavailable) +extension CustomViewCountCache: Sendable {} +#endif diff --git a/Sources/OpenSwiftUICore/View/Input/ViewList.swift b/Sources/OpenSwiftUICore/View/Input/ViewList.swift index ca1480c4e..b1d9260ef 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewList.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewList.swift @@ -112,13 +112,26 @@ public struct _ViewListCountInputs { package var customInputs: PropertyList package var options: _ViewListInputs.Options package var baseOptions: _GraphInputs.Options + #if OPENSWIFTUI_SUPPORT_2025_API + package var customViewCache: CustomViewCountCache? + package var debugReplaceableViewInfo: DebugReplaceableViewInfo + #else package var customModifierTypes: [ObjectIdentifier] + #endif package init(_ inputs: _ViewListInputs) { customInputs = inputs.base.customInputs options = inputs.options baseOptions = inputs.base.options + #if OPENSWIFTUI_SUPPORT_2025_API + customViewCache = nil + debugReplaceableViewInfo = DebugReplaceableViewInfo( + countAsZero: false, + countedAsZero: nil + ) + #else customModifierTypes = [] + #endif } package subscript(input: T.Type) -> T.Value where T: GraphInput {