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
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions Sources/OpenSwiftUICore/Data/Binding/Binding+ObjectLocation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Binding+ObjectLocation.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: 7719FABF28E05207C06C2817640AD611 (SwiftUICore)

import Foundation

extension Binding {
init<ObjectType: AnyObject>(
_ root: ObjectType,
keyPath: ReferenceWritableKeyPath<ObjectType, Value>,
isolation: (any Actor)? = #isolation
) {
let location = ObjectLocation(base: root, keyPath: keyPath, isolation: isolation)
let box = LocationBox(location)
self.init(value: location.get(), location: box)
}
}

private struct ObjectLocation<Root, Value>: Location where Root: AnyObject {
var base: Root

var keyPath: ReferenceWritableKeyPath<Root, Value>

var isolation: (any Actor)?

var wasRead: Bool {
get { true }
nonmutating set {}
}

func get() -> Value {
checkIsolation()
return base[keyPath: keyPath]
}

func set(_ value: Value, transaction: Transaction) {
withTransaction(transaction) {
checkIsolation()
base[keyPath: keyPath] = value
}
}

static func == (_ lhs: ObjectLocation, _ rhs: ObjectLocation) -> Bool {
lhs.base === rhs.base && lhs.keyPath == rhs.keyPath
}

func checkIsolation() {
guard let isolation, isolation === MainActor.shared, !Thread.isMainThread else {
return
}
let description = String(describing: keyPath)
Log.runtimeIssues(
"%s is isolated to the main actor. Accessing it via Binding from a different actor will cause undefined behaviors, and potential data races; This warning will become a runtime crash in a future version of OpenSwiftUI.",
[description]
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// AttributeInvalidatingSubscriber.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete

import Foundation
import OpenAttributeGraphShims
#if OPENSWIFTUI_OPENCOMBINE
import OpenCombine
#else
import Combine
#endif

// MARK: - AttributeInvalidatingSubscriber

class AttributeInvalidatingSubscriber<Upstream> where Upstream: Publisher {
typealias Input = Upstream.Output

typealias Failure = Upstream.Failure

// MARK: - StateType

enum StateType {
case subscribed(any Subscription)
case unsubscribed
case complete
}

weak var host: GraphHost?

let attribute: WeakAttribute<()>

var state: StateType

init(host: GraphHost, attribute: WeakAttribute<()>) {
self.host = host
self.attribute = attribute
self.state = .unsubscribed
}

private func invalidateAttribute() {
let style: GraphMutation.Style
if !Thread.isMainThread {
Log.runtimeIssues("Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.")
style = .immediate
} else if Update.threadIsUpdating, isLinkedOnOrAfter(.v4) {
Log.runtimeIssues("Publishing changes from within view updates is not allowed, this will cause undefined behavior.")
style = .deferred
} else {
style = .immediate
}
Update.perform {
guard let host else { return }
host.asyncTransaction(
.current,
invalidating: attribute,
style: style
)
}
}
}

// MARK: - AttributeInvalidatingSubscriber + Subscriber

extension AttributeInvalidatingSubscriber: Subscriber {
func receive(subscription: any Subscription) {
guard case .unsubscribed = state else {
subscription.cancel()
return
}
state = .subscribed(subscription)
subscription.request(.unlimited)
}

func receive(_ input: Input) -> Subscribers.Demand {
if case .subscribed = state {
invalidateAttribute()
}
return .none
}

func receive(completion: Subscribers.Completion<Failure>) {
guard case .subscribed = state else {
return
}
state = .complete
invalidateAttribute()
}
}

// MARK: - AttributeInvalidatingSubscriber + Cancellable

extension AttributeInvalidatingSubscriber: Cancellable {
func cancel() {
if case let .subscribed(subscription) = state {
subscription.cancel()
}
state = .unsubscribed
}
}

// MARK: - AttributeInvalidatingSubscriber + CustomCombineIdentifierConvertible

extension AttributeInvalidatingSubscriber: CustomCombineIdentifierConvertible {}
163 changes: 163 additions & 0 deletions Sources/OpenSwiftUICore/Data/Combine/SubscriptionLifetime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//
// SubscriptionLifetime.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: 6C59EBF8CD01332EB851D19EA2F31D6B (SwiftUICore)

import OpenAttributeGraphShims
#if OPENSWIFTUI_OPENCOMBINE
import OpenCombine
#else
import Combine
#endif

// MARK: - SubscriptionLifetime

class SubscriptionLifetime<Upstream>: Cancellable where Upstream: Publisher {

// MARK: - Connection

private struct Connection<Downstream>: Subscriber, CustomCombineIdentifierConvertible
where Downstream: Subscriber, Upstream.Failure == Downstream.Failure, Upstream.Output == Downstream.Input {
typealias Input = Downstream.Input

typealias Failure = Downstream.Failure

var combineIdentifier: CombineIdentifier = .init()

weak var parent: SubscriptionLifetime?

let downstream: Downstream

let subscriptionID: Int

init(
parent: SubscriptionLifetime,
downstream: Downstream,
subscriptionID: Int
) {
self.parent = parent
self.downstream = downstream
self.subscriptionID = subscriptionID
}

func receive(subscription: any Subscription) {
guard let parent,
parent.shouldAcceptSubscription(subscription, for: subscriptionID) else {
return
}
downstream.receive(subscription: subscription)
subscription.request(.unlimited)
}

func receive(_ input: Input) -> Subscribers.Demand {
guard let parent,
parent.shouldAcceptValue(for: subscriptionID) else {
return .none
}
_ = downstream.receive(input)
return .none
}

func receive(completion: Subscribers.Completion<Failure>) {
guard let parent,
parent.shouldAcceptCompletion(for: subscriptionID) else {
return
}
downstream.receive(completion: completion)
}
}

// MARK: - StateType

enum StateType {
case requestedSubscription(to: Upstream, subscriber: AnyCancellable, subscriptionID: Int)
case subscribed(to: Upstream, subscriber: AnyCancellable, subscription: Subscription, subscriptionID: Int)
case uninitialized
}

var subscriptionID: UniqueSeedGenerator = .init()

var state: StateType = .uninitialized

init() {
_openSwiftUIEmptyStub()
}

deinit {
cancel()
}

var isUninitialized: Bool {
guard case .uninitialized = state else {
return false
}
return true
}

private func shouldAcceptSubscription(_ subscription: any Subscription, for subscriptionID: Int) -> Bool {
guard case let .requestedSubscription(oldPublisher, oldSubscriber, oldSubscriptionID) = state,
oldSubscriptionID == subscriptionID else {
subscription.cancel()
return false
}
state = .subscribed(
to: oldPublisher,
subscriber: oldSubscriber,
subscription: subscription,
subscriptionID: subscriptionID
)
return true
}

private func shouldAcceptValue(for subscriptionID: Int) -> Bool {
guard case .subscribed = state else {
return false
}
return true
}

private func shouldAcceptCompletion(for subscriptionID: Int) -> Bool {
guard case let .subscribed(_, _, _, oldSubscriptionID) = state,
subscriptionID == oldSubscriptionID else {
return false
}
state = .uninitialized
return true
}

func cancel() {
guard case let .subscribed(_, subscriber, subscription, _) = state else {
return
}
subscriber.cancel()
subscription.cancel()
}

func subscribe<S>(
subscriber: S,
to upstream: Upstream
) where S: Cancellable, S: Subscriber, Upstream.Failure == S.Failure, Upstream.Output == S.Input {
let shouldRequest: Bool
if case let .subscribed(oldUpstream, oldSubscriber, oldSubscription, _) = state {
if compareValues(oldUpstream, upstream) {
shouldRequest = false
} else {
oldSubscriber.cancel()
oldSubscription.cancel()
shouldRequest = true
}
} else {
shouldRequest = true
}
guard shouldRequest else {
return
}
let id = subscriptionID.generate()
let connection = Connection(parent: self, downstream: subscriber, subscriptionID: id)
state = .requestedSubscription(to: upstream, subscriber: .init(subscriber), subscriptionID: id)
upstream.subscribe(connection)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ public protocol DynamicProperty {

package struct DynamicPropertyBehaviors: OptionSet {
package let rawValue: UInt32

package static let allowsAsync = DynamicPropertyBehaviors(rawValue: 1 << 0)

package static let requiresMainThread = DynamicPropertyBehaviors(rawValue: 1 << 1)

package init(rawValue: UInt32) {
self.rawValue = rawValue
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/Data/Environment/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ private struct EnvironmentBox<Value>: DynamicPropertyBox {
guard case let .keyPath(propertyKeyPath) = property.content else {
return false
}
let (environment, environmentChanged) = _environment.changedValue(options: [])
let (environment, environmentChanged) = _environment.changedValue()
let keyPathChanged = (propertyKeyPath != keyPath)
if keyPathChanged { keyPath = propertyKeyPath }
let valueChanged: Bool
Expand Down
Loading
Loading