-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
59 changed files
with
4,204 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
Sources/ComposableArchitecture/Observation/ObservableState.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
Sources/ComposableArchitecture/Observation/Observation+State.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import CasePaths | ||
import Perception | ||
import KPerception | ||
import SwiftUI | ||
import XCTestDynamicOverlay | ||
|
||
|
2 changes: 1 addition & 1 deletion
2
Sources/ComposableArchitecture/Observation/ObservationStateRegistrar.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/// A generic wrapper for isolating a mutable value to an actor. | ||
/// | ||
/// This type is most useful when writing tests for when you want to inspect what happens inside an | ||
/// async operation. | ||
/// | ||
/// For example, suppose you have a feature such that when a button is tapped you track some | ||
/// analytics: | ||
/// | ||
/// ```swift | ||
/// struct AnalyticsClient { | ||
/// var track: (String) async -> Void | ||
/// } | ||
/// | ||
/// class FeatureModel: ObservableObject { | ||
/// let analytics: AnalyticsClient | ||
/// // ... | ||
/// func buttonTapped() { | ||
/// // ... | ||
/// await self.analytics.track("Button tapped") | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// Then, in tests we can construct an analytics client that appends events to a mutable array | ||
/// rather than actually sending events to an analytics server. However, in order to do this in a | ||
/// safe way we should use an actor, and `ActorIsolated` makes this easy: | ||
/// | ||
/// ```swift | ||
/// func testAnalytics() async { | ||
/// let events = ActorIsolated<[String]>([]) | ||
/// let analytics = AnalyticsClient( | ||
/// track: { event in await events.withValue { $0.append(event) } } | ||
/// ) | ||
/// let model = FeatureModel(analytics: analytics) | ||
/// model.buttonTapped() | ||
/// await events.withValue { | ||
/// XCTAssertEqual($0, ["Button tapped"]) | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// To synchronously isolate a value, see ``LockIsolated``. | ||
@available(*, deprecated, message: "Use 'LockIsolated' instead.") | ||
public final actor ActorIsolated<Value> { | ||
/// The actor-isolated value. | ||
public var value: Value | ||
|
||
/// Initializes actor-isolated state around a value. | ||
/// | ||
/// - Parameter value: A value to isolate in an actor. | ||
public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { | ||
self.value = try value() | ||
} | ||
|
||
/// Perform an operation with isolated access to the underlying value. | ||
/// | ||
/// Useful for modifying a value in a single transaction. | ||
/// | ||
/// ```swift | ||
/// // Isolate an integer for concurrent read/write access: | ||
/// let count = ActorIsolated(0) | ||
/// | ||
/// func increment() async { | ||
/// // Safely increment it: | ||
/// await self.count.withValue { $0 += 1 } | ||
/// } | ||
/// ``` | ||
/// | ||
/// > Tip: Because XCTest assertions don't play nicely with Swift concurrency, `withValue` also | ||
/// > provides a handy interface to peek at an actor-isolated value and assert against it: | ||
/// > | ||
/// > ```swift | ||
/// > let didOpenSettings = ActorIsolated(false) | ||
/// > let model = withDependencies { | ||
/// > $0.openSettings = { await didOpenSettings.setValue(true) } | ||
/// > } operation: { | ||
/// > FeatureModel() | ||
/// > } | ||
/// > await model.settingsButtonTapped() | ||
/// > await didOpenSettings.withValue { XCTAssertTrue($0) } | ||
/// > ``` | ||
/// | ||
/// - Parameter operation: An operation to be performed on the actor with the underlying value. | ||
/// - Returns: The result of the operation. | ||
public func withValue<T>( | ||
_ operation: @Sendable (inout Value) throws -> T | ||
) rethrows -> T { | ||
var value = self.value | ||
defer { self.value = value } | ||
return try operation(&value) | ||
} | ||
|
||
/// Overwrite the isolated value with a new value. | ||
/// | ||
/// ```swift | ||
/// // Isolate an integer for concurrent read/write access: | ||
/// let count = ActorIsolated(0) | ||
/// | ||
/// func reset() async { | ||
/// // Reset it: | ||
/// await self.count.setValue(0) | ||
/// } | ||
/// ``` | ||
/// | ||
/// > Tip: Use ``withValue(_:)`` instead of `setValue` if the value being set is derived from the | ||
/// > current value. This isolates the entire transaction and avoids data races between reading | ||
/// > and writing the value. | ||
/// | ||
/// - Parameter newValue: The value to replace the current isolated value with. | ||
public func setValue(_ newValue: @autoclosure @Sendable () throws -> Value) rethrows { | ||
self.value = try newValue() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/// A type-erased hashable, sendable value. | ||
/// | ||
/// A sendable version of `AnyHashable` that is useful in working around the limitation that an | ||
/// existential `any Hashable` does not conform to `Hashable`. | ||
public struct AnyHashableSendable: Hashable, Sendable { | ||
public let base: any Hashable & Sendable | ||
|
||
/// Creates a type-erased hashable, sendable value that wraps the given instance. | ||
public init(_ base: some Hashable & Sendable) { | ||
if let base = base as? AnyHashableSendable { | ||
self = base | ||
} else { | ||
self.base = base | ||
} | ||
} | ||
|
||
public static func == (lhs: Self, rhs: Self) -> Bool { | ||
AnyHashable(lhs.base) == AnyHashable(rhs.base) | ||
} | ||
|
||
public func hash(into hasher: inout Hasher) { | ||
hasher.combine(base) | ||
} | ||
} | ||
|
||
extension AnyHashableSendable: CustomDebugStringConvertible { | ||
public var debugDescription: String { | ||
"AnyHashableSendable(" + String(reflecting: base) + ")" | ||
} | ||
} | ||
|
||
extension AnyHashableSendable: CustomReflectable { | ||
public var customMirror: Mirror { | ||
Mirror(self, children: ["value": base]) | ||
} | ||
} | ||
|
||
extension AnyHashableSendable: CustomStringConvertible { | ||
public var description: String { | ||
String(describing: base) | ||
} | ||
} |
Oops, something went wrong.