Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/Data/Combine/ObservedObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public struct ObservedObject<ObjectType>: 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<V>(
in buffer: inout _DynamicPropertyBuffer,
container: _GraphValue<V>,
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/Data/Combine/StateObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ public struct StateObject<ObjectType>: DynamicProperty where ObjectType: Observa
inputs: inout _GraphInputs
) {
var buf = _DynamicPropertyBuffer()
#if OPENSWIFTUI_RELEASE_2025
#if OPENSWIFTUI_SUPPORT_2025_API
let attribute = ObservedObject<ObjectType>.makeBoxAndSignal(in: &buf, container: container, fieldOffset: 0)
buffer.append(Box(links: buf, object: nil), fieldOffset: fieldOffset)
addTreeValue(
Expand Down
7 changes: 7 additions & 0 deletions Sources/OpenSwiftUICore/Modifier/CustomViewModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ public struct PlaceholderContentView<Value>: 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
Expand Down Expand Up @@ -218,6 +223,7 @@ extension ViewModifierContentProvider {
}
}

#if !OPENSWIFTUI_SUPPORT_2025_API
nonisolated fileprivate static func providerViewListCount(
inputs: _ViewListCountInputs
) -> Int? {
Expand All @@ -231,6 +237,7 @@ extension ViewModifierContentProvider {
inputs.customModifierTypes.append(ObjectIdentifier(Self.self))
return input(inputs)
}
#endif
}

extension _GraphInputs {
Expand Down
196 changes: 196 additions & 0 deletions Sources/OpenSwiftUICore/View/DynamicView/DebugReplaceableView.swift
Original file line number Diff line number Diff line change
@@ -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<V>(erasing view: V) where V: View {
self.init(_erasing: view)
}

@usableFromInline
internal init<V>(_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<Self>, inputs: _ViewInputs) -> _ViewOutputs {
makeDynamicView(metadata: (), view: view, inputs: inputs)
}

nonisolated public static func _makeViewList(view: _GraphValue<Self>, 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: ViewVisitor>(_ 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<DebugReplaceableView>, inputs: _ViewInputs) -> _ViewOutputs {
storage.makeChildView(view: view, inputs: inputs)
}

package func makeChildViewList(metadata: (), view: Attribute<DebugReplaceableView>, 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<DebugReplaceableView>, inputs: _ViewInputs) -> _ViewOutputs {
_openSwiftUIBaseClassAbstractMethod()
}

fileprivate func makeChildViewList(view: Attribute<DebugReplaceableView>, inputs: _ViewListInputs) -> _ViewListOutputs {
_openSwiftUIBaseClassAbstractMethod()
}

fileprivate func visitContent<Visitor>(_ visitor: inout Visitor) where Visitor: ViewVisitor {
_openSwiftUIBaseClassAbstractMethod()
}
}

@available(*, unavailable)
extension DebugReplaceableViewStorageBase: Sendable {}

// MARK: - DebugReplaceableViewStorage

private final class DebugReplaceableViewStorage<V>: 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<DebugReplaceableView>,
inputs: _ViewInputs
) -> _ViewOutputs {
var inputs = inputs
inputs.base.pushStableType(V.self)
let childView = Attribute(DebugReplaceableViewChild<V>(view: view))
childView.value = self.view
return V.makeDebuggableView(view: _GraphValue(childView), inputs: inputs)
}

override func makeChildViewList(
view: Attribute<DebugReplaceableView>,
inputs: _ViewListInputs
) -> _ViewListOutputs {
var inputs = inputs
inputs.base.pushStableType(V.self)
let childView = Attribute(DebugReplaceableViewChild<V>(view: view))
childView.value = self.view
return V.makeDebuggableViewList(view: _GraphValue(childView), inputs: inputs)
}

override func visitContent<Visitor>(_ visitor: inout Visitor) where Visitor : ViewVisitor {
visitor.visit(view)
}
}

// MARK: - DebugReplaceableViewInfo

package struct DebugReplaceableViewInfo {
package var countAsZero: Bool
package var countedAsZero: UnsafeMutablePointer<Bool>?

package init(countAsZero: Bool, countedAsZero: UnsafeMutablePointer<Bool>?) {
self.countAsZero = countAsZero
self.countedAsZero = countedAsZero
}
}

// MARK: - DebugReplaceableViewChild

fileprivate struct DebugReplaceableViewChild<V>: StatefulRule, AsyncAttribute, CustomStringConvertible where V: View {
@Attribute var view: DebugReplaceableView

typealias Value = V

func updateValue() {
value = (view.storage as! DebugReplaceableViewStorage<V>).view
}

var description: String {
"\(V.self)"
}
}
#endif
2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/View/DynamicView/DynamicView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>, inputs: _ViewInputs) -> _ViewOutputs
Expand Down
98 changes: 98 additions & 0 deletions Sources/OpenSwiftUICore/View/Input/CustomViewCountCache.swift
Original file line number Diff line number Diff line change
@@ -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<Count>

/// 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<Count>?

init(id: ObjectIdentifier, count: Int?? = nil, next: UnsafeMutablePointer<Count>? = nil) {
self.id = id
self.count = count
self.next = next
}
}
}

@available(*, unavailable)
extension CustomViewCountCache: Sendable {}
#endif
13 changes: 13 additions & 0 deletions Sources/OpenSwiftUICore/View/Input/ViewList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(input: T.Type) -> T.Value where T: GraphInput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Status: Complete
// ID: 39057DDA72E946BD17E1F42CCA55F7F6 (SwiftUICore)

#if OPENSWIFTUI_RELEASE_2024
#if OPENSWIFTUI_SUPPORT_2024_API

// MARK: - InterfaceIdiom

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Audited for 6.0.87
// Status: Complete

#if OPENSWIFTUI_RELEASE_2024
#if OPENSWIFTUI_SUPPORT_2024_API
package struct InterfaceIdiomPredicate<Idiom>: ViewInputPredicate where Idiom: InterfaceIdiom {
package init() {}

Expand Down
Loading
Loading