Initial Commit
This commit is contained in:
commit
e1463eb256
4 changed files with 146 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/configuration/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
||||||
24
Package.swift
Normal file
24
Package.swift
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// swift-tools-version: 6.1
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "ArtifactState",
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "ArtifactState",
|
||||||
|
targets: ["ArtifactState"]),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
|
.target(
|
||||||
|
name: "ArtifactState"),
|
||||||
|
.testTarget(
|
||||||
|
name: "ArtifactStateTests",
|
||||||
|
dependencies: ["ArtifactState"]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
102
Sources/ArtifactState/State.swift
Normal file
102
Sources/ArtifactState/State.swift
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
|
||||||
|
actor ObservationState {
|
||||||
|
public static var observers: [[any BindingProtocol]] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
public func observing<T>(into: inout [any BindingProtocol], body: () -> T) -> T {
|
||||||
|
ObservationState.observers.append([])
|
||||||
|
defer { into = ObservationState.observers.popLast()! }
|
||||||
|
return body()
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol BindingProtocol {
|
||||||
|
func notify()
|
||||||
|
func subscribe(key: ObjectIdentifier, handler: @escaping () -> Void)
|
||||||
|
func unsubscribe(key: ObjectIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Binding<T: Equatable>: BindingProtocol {
|
||||||
|
public private(set) var listeners: [ObjectIdentifier: () -> Void] = [:]
|
||||||
|
public var wrappedValue: T { fatalError() }
|
||||||
|
|
||||||
|
public func notify() {
|
||||||
|
listeners.forEach { $1() }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func subscribe(key: ObjectIdentifier, handler: @escaping () -> Void) {
|
||||||
|
listeners[key] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unsubscribe(key: ObjectIdentifier) {
|
||||||
|
listeners.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@propertyWrapper
|
||||||
|
public class State<T: Equatable>: Binding<T> {
|
||||||
|
public var projectedValue: State<T> { self }
|
||||||
|
private var value: T
|
||||||
|
public override var wrappedValue: T {
|
||||||
|
get {
|
||||||
|
if ObservationState.observers.count > 0 {
|
||||||
|
ObservationState.observers[ObservationState.observers.count - 1].append(self)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if value != newValue {
|
||||||
|
value = newValue
|
||||||
|
notify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(wrappedValue: T) {
|
||||||
|
self.value = wrappedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DerivedState<T: Equatable>: Binding<T> {
|
||||||
|
private var value: T
|
||||||
|
public override var wrappedValue: T {
|
||||||
|
get {
|
||||||
|
if ObservationState.observers.count > 0 {
|
||||||
|
ObservationState.observers[ObservationState.observers.count - 1].append(self)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var deps: [any BindingProtocol] = []
|
||||||
|
var derivation: () -> T
|
||||||
|
private var id: ObjectIdentifier!
|
||||||
|
|
||||||
|
public init(_ derivation: @escaping () -> T) {
|
||||||
|
self.derivation = derivation
|
||||||
|
value = observing(into: &deps) {
|
||||||
|
derivation()
|
||||||
|
}
|
||||||
|
super.init()
|
||||||
|
id = ObjectIdentifier(self)
|
||||||
|
deps.forEach {
|
||||||
|
$0.subscribe(key: id) { [self] in
|
||||||
|
recompute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
deps.forEach { $0.unsubscribe(key: id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func recompute() {
|
||||||
|
deps.forEach { $0.unsubscribe(key: id) }
|
||||||
|
value = observing(into: &deps) {
|
||||||
|
derivation()
|
||||||
|
}
|
||||||
|
deps.forEach {
|
||||||
|
$0.subscribe(key: id) { [self] in
|
||||||
|
recompute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Tests/ArtifactStateTests/ArtifactStateTests.swift
Normal file
12
Tests/ArtifactStateTests/ArtifactStateTests.swift
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import ArtifactState
|
||||||
|
|
||||||
|
final class ArtifactStateTests: XCTestCase {
|
||||||
|
func testExample() throws {
|
||||||
|
// XCTest Documentation
|
||||||
|
// https://developer.apple.com/documentation/xctest
|
||||||
|
|
||||||
|
// Defining Test Cases and Test Methods
|
||||||
|
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue