Skip to content

Instantly share code, notes, and snippets.

@markmals
Last active March 24, 2024 06:02
Show Gist options
  • Save markmals/3ced194c7fb891085b6f1bd3ca61a6e4 to your computer and use it in GitHub Desktop.
Save markmals/3ced194c7fb891085b6f1bd3ca61a6e4 to your computer and use it in GitHub Desktop.
An implementation of Solid.js's signals API using Swift's @observable macro
import Foundation
import Observation
@Observable
final class Signal<T> {
var value: T
init(value: T) {
self.value = value
}
}
public func createSignal<T>(_ initialValue: T) -> (() -> T, (T) -> Void) {
let signal = Signal(value: initialValue)
return (
{ return signal.value },
{ newValue in signal.value = newValue }
)
}
public func createEffect(_ apply: @escaping () -> Void) {
@Sendable func observe() {
withObservationTracking {
apply()
} onChange: {
DispatchQueue.main.async {
observe()
}
}
}
observe()
}
enum Box<T>: RawRepresentable {
case initialized(T)
case uninitialized
var rawValue: T {
switch self {
case .initialized(let value): return value
case .uninitialized: fatalError("Memoized function accessed before initialization")
}
}
init(rawValue: T) {
self = .initialized(rawValue)
}
}
public func createMemo<T: Equatable>(_ computation: @autoclosure @escaping () -> T) -> () -> T {
let signal = Signal<Box<T>>(value: .uninitialized)
createEffect {
let value = computation()
guard case .initialized(_) = signal.value else {
signal.value = .initialized(value)
return
}
if value != signal.value.rawValue {
signal.value = .initialized(value)
}
}
return { return signal.value.rawValue }
}
@markmals
Copy link
Author

Example usage:

let (count, setCount) = createSignal(0)
let doubleCount = createMemo(count() * 2)

createEffect {
    print("Double \(count()) is \(doubleCount())")
}

let _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    setCount(count() + 1)
}

RunLoop.main.run()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment