Merge pull request #24 from phranck/refactor/singleton-elimination-phase1

Refactor: Remove 5 deprecated singletons (Phase 1)
This commit is contained in:
phranck
2026-01-30 22:58:24 +01:00
4 changed files with 19 additions and 174 deletions
+19 -2
View File
@@ -89,6 +89,9 @@ public final class EnvironmentStorage: @unchecked Sendable {
/// The shared environment storage.
public static let shared = EnvironmentStorage()
/// Lock protecting all mutable state.
private let lock = NSLock()
/// The current environment values.
private var current = EnvironmentValues()
@@ -99,20 +102,32 @@ public final class EnvironmentStorage: @unchecked Sendable {
/// The current environment values.
public var environment: EnvironmentValues {
get { current }
set { current = newValue }
get {
lock.lock()
defer { lock.unlock() }
return current
}
set {
lock.lock()
defer { lock.unlock() }
current = newValue
}
}
/// Pushes a new environment onto the stack.
///
/// - Parameter environment: The environment to push.
public func push(_ environment: EnvironmentValues) {
lock.lock()
defer { lock.unlock() }
stack.append(current)
current = environment
}
/// Pops the current environment and restores the previous one.
public func pop() {
lock.lock()
defer { lock.unlock() }
if let previous = stack.popLast() {
current = previous
}
@@ -132,6 +147,8 @@ public final class EnvironmentStorage: @unchecked Sendable {
/// Resets the environment to its initial state.
public func reset() {
lock.lock()
defer { lock.unlock() }
current = EnvironmentValues()
stack.removeAll()
}
-4
View File
@@ -250,10 +250,6 @@ extension KeyEvent {
/// Views can register handlers that are called when keys are pressed.
/// Handlers are processed in reverse order (most recent first).
public final class KeyEventDispatcher: @unchecked Sendable {
/// The shared dispatcher instance.
@available(*, deprecated, message: "Use TUIContext.keyEventDispatcher instead")
public static let shared = KeyEventDispatcher()
/// Registered key handlers.
private var handlers: [(KeyEvent) -> Bool] = []
-4
View File
@@ -95,10 +95,6 @@ public struct PreferenceValues: @unchecked Sendable {
/// Thread-local storage for collecting preferences during rendering.
public final class PreferenceStorage: @unchecked Sendable {
/// The shared preference storage.
@available(*, deprecated, message: "Use TUIContext.preferences instead")
public static let shared = PreferenceStorage()
/// Stack of preference values for nested rendering.
private var stack: [PreferenceValues] = [PreferenceValues()]
@@ -7,92 +7,6 @@
import Foundation
// MARK: - Lifecycle Tracker
/// Tracks which views have appeared to prevent duplicate onAppear calls.
///
/// Since views are recreated on each render, we use a token-based system
/// to track which views have already triggered their onAppear action.
public final class LifecycleTracker: @unchecked Sendable {
/// Shared instance for the running application.
@available(*, deprecated, message: "Use TUIContext.lifecycle instead")
public static let shared = LifecycleTracker()
/// Lock protecting all mutable state.
private let lock = NSLock()
/// Set of tokens that have appeared.
private var appearedTokens: Set<String> = []
/// Set of tokens that are currently visible (for onDisappear tracking).
private var visibleTokens: Set<String> = []
/// Tokens seen during the current render pass.
private var currentRenderTokens: Set<String> = []
/// Creates a new lifecycle tracker.
public init() {}
/// Marks the start of a new render pass.
internal func beginRenderPass() {
lock.lock()
defer { lock.unlock() }
currentRenderTokens.removeAll()
}
/// Marks the end of a render pass and triggers onDisappear for views that are no longer visible.
internal func endRenderPass(onDisappear: [String: () -> Void]) {
lock.lock()
let disappearedTokens = visibleTokens.subtracting(currentRenderTokens)
for token in disappearedTokens {
appearedTokens.remove(token)
}
visibleTokens = currentRenderTokens
lock.unlock()
// Execute callbacks outside the lock to avoid deadlocks
for token in disappearedTokens {
onDisappear[token]?()
}
}
/// Records that a view with the given token appeared.
///
/// - Parameters:
/// - token: Unique identifier for the view.
/// - action: The onAppear action to execute.
/// - Returns: True if this is the first appearance (action should run).
internal func recordAppear(token: String, action: () -> Void) -> Bool {
lock.lock()
currentRenderTokens.insert(token)
if !appearedTokens.contains(token) {
appearedTokens.insert(token)
lock.unlock()
action()
return true
}
lock.unlock()
return false
}
/// Checks if a view has appeared before.
internal func hasAppeared(token: String) -> Bool {
lock.lock()
defer { lock.unlock() }
return appearedTokens.contains(token)
}
/// Resets all tracking state.
internal func reset() {
lock.lock()
defer { lock.unlock() }
appearedTokens.removeAll()
visibleTokens.removeAll()
currentRenderTokens.removeAll()
}
}
// MARK: - OnAppear Modifier
/// A modifier that executes an action when a view first appears.
@@ -123,43 +37,6 @@ extension OnAppearModifier: Renderable {
// MARK: - OnDisappear Modifier
/// Storage for onDisappear callbacks.
public final class DisappearCallbackStorage: @unchecked Sendable {
@available(*, deprecated, message: "Use TUIContext.lifecycle instead")
public static let shared = DisappearCallbackStorage()
/// Lock protecting the callbacks dictionary.
private let lock = NSLock()
private var callbacks: [String: () -> Void] = [:]
/// Creates a new disappear callback storage.
public init() {}
internal func register(token: String, action: @escaping () -> Void) {
lock.lock()
defer { lock.unlock() }
callbacks[token] = action
}
internal func unregister(token: String) {
lock.lock()
defer { lock.unlock() }
callbacks.removeValue(forKey: token)
}
internal var allCallbacks: [String: () -> Void] {
lock.lock()
defer { lock.unlock() }
return callbacks
}
internal func reset() {
lock.lock()
defer { lock.unlock() }
callbacks.removeAll()
}
}
/// A modifier that executes an action when a view disappears.
public struct OnDisappearModifier<Content: View>: View {
/// The content view.
@@ -212,47 +89,6 @@ public struct TaskModifier<Content: View>: View {
}
}
/// Storage for running tasks.
public final class TaskStorage: @unchecked Sendable {
@available(*, deprecated, message: "Use TUIContext.lifecycle instead")
public static let shared = TaskStorage()
/// Lock protecting the tasks dictionary.
private let lock = NSLock()
private var tasks: [String: Task<Void, Never>] = [:]
/// Creates a new task storage.
public init() {}
internal func startTask(token: String, priority: TaskPriority, operation: @escaping @Sendable () async -> Void) {
lock.lock()
// Cancel existing task if any
tasks[token]?.cancel()
// Start new task
tasks[token] = Task(priority: priority) {
await operation()
}
lock.unlock()
}
internal func cancelTask(token: String) {
lock.lock()
tasks[token]?.cancel()
tasks.removeValue(forKey: token)
lock.unlock()
}
internal func reset() {
lock.lock()
for task in tasks.values {
task.cancel()
}
tasks.removeAll()
lock.unlock()
}
}
extension TaskModifier: Renderable {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
let lifecycle = context.tuiContext.lifecycle