Merge pull request #1 from AparokshaUI/reference-state-management

Switch to state management using reference types
This commit is contained in:
david-swift 2024-08-31 15:20:04 +02:00 committed by GitHub
commit 46f5c3289b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 46 additions and 145 deletions

View File

@ -61,7 +61,7 @@ public protocol Model {
public struct ModelData { public struct ModelData {
/// The state value's identifier. /// The state value's identifier.
var id: String var storage: StateContent.Storage
/// Whether to force update the views when this value changes. /// Whether to force update the views when this value changes.
var force: Bool var force: Bool
@ -78,8 +78,8 @@ extension Model {
guard let data = model else { guard let data = model else {
return return
} }
StateManager.setState(id: data.id, value: newValue) data.storage.value = newValue
StateManager.updateState(id: data.id) data.storage.update = true
StateManager.updateViews(force: data.force) StateManager.updateViews(force: data.force)
} }
} }
@ -98,8 +98,8 @@ extension Model {
} }
var model = getModel() var model = getModel()
setModel(&model) setModel(&model)
StateManager.setState(id: data.id, value: model) data.storage.value = model
StateManager.updateState(id: data.id) data.storage.update = true
StateManager.updateViews(force: data.force) StateManager.updateViews(force: data.force)
} }
@ -112,7 +112,7 @@ extension Model {
guard let data = model else { guard let data = model else {
return self return self
} }
return StateManager.getState(id: data.id) as? Self ?? self return data.storage.value as? Self ?? self
} }
} }

View File

@ -18,7 +18,7 @@ public struct State<Value>: StateProtocol {
} }
nonmutating set { nonmutating set {
rawValue = newValue rawValue = newValue
StateManager.updateState(id: id) content.update = true
StateManager.updateViews(force: forceUpdates) StateManager.updateViews(force: forceUpdates)
} }
} }
@ -35,42 +35,42 @@ public struct State<Value>: StateProtocol {
/// Get and set the value without updating the views. /// Get and set the value without updating the views.
public var rawValue: Value { public var rawValue: Value {
get { get {
guard let value = StateManager.getState(id: id) as? Value else { guard let value = content.value as? Value else {
let initialValue = getInitialValue() let initialValue = getInitialValue()
let storage = StateContent.Storage(value: initialValue)
if var model = initialValue as? Model { if var model = initialValue as? Model {
model.model = .init(id: id, force: forceUpdates) model.model = .init(storage: storage, force: forceUpdates)
model.setup() model.setup()
StateManager.setState(id: id, value: model) content.storage = storage
StateManager.addConstantID(id) content.value = model
} else { } else {
StateManager.setState(id: id, value: initialValue) content.storage = storage
} }
return initialValue return initialValue
} }
return value return value
} }
nonmutating set { nonmutating set {
StateManager.setState(id: id, value: newValue) content.value = newValue
} }
} }
/// The state's identifier for the stored value.
var id: String
/// Whether to force update the views when the value changes. /// Whether to force update the views when the value changes.
var forceUpdates: Bool var forceUpdates: Bool
/// The closure for initializing the state property's value. /// The closure for initializing the state property's value.
var getInitialValue: () -> Value var getInitialValue: () -> Value
/// The content.
let content: StateContent = .init()
/// Initialize a property representing a state in the view with an autoclosure. /// Initialize a property representing a state in the view with an autoclosure.
/// - Parameters: /// - Parameters:
/// - wrappedValue: The wrapped value. /// - wrappedValue: The wrapped value.
/// - id: An explicit identifier. /// - id: An explicit identifier.
/// - forceUpdates: Whether to force update all available views when the property gets modified. /// - forceUpdates: Whether to force update all available views when the property gets modified.
public init(wrappedValue: @autoclosure @escaping () -> Value, id: String? = nil, forceUpdates: Bool = false) { public init(wrappedValue: @autoclosure @escaping () -> Value, forceUpdates: Bool = false) {
getInitialValue = wrappedValue getInitialValue = wrappedValue
self.id = id ?? UUID().uuidString
self.forceUpdates = forceUpdates self.forceUpdates = forceUpdates
} }

View File

@ -9,30 +9,34 @@
class StateContent { class StateContent {
/// The storage. /// The storage.
var storage: Storage { var storage: Storage?
/// The value.
var value: Any? {
get { get {
if let internalStorage { storage?.value
return internalStorage
}
let value = getInitialValue()
let storage = Storage(value: value)
internalStorage = storage
return storage
} }
set { set {
internalStorage = newValue if let storage {
storage.value = newValue as Any
} else {
storage = .init(value: newValue as Any)
}
}
}
/// Whether to update the views.
var update: Bool {
get {
storage?.update ?? false
}
set {
storage?.update = newValue
} }
} }
/// The internal storage.
var internalStorage: Storage?
/// The initial value.
private var getInitialValue: () -> Any
/// Initialize the content without already initializing the storage or initializing the value. /// Initialize the content without already initializing the storage or initializing the value.
/// - Parameter initialValue: The initial value. init() { }
init(getInitialValue: @escaping () -> Any) {
self.getInitialValue = getInitialValue
}
/// A class storing the value. /// A class storing the value.
class Storage { class Storage {

View File

@ -12,41 +12,10 @@ public enum StateManager {
/// Whether to block updates in general. /// Whether to block updates in general.
public static var blockUpdates = false public static var blockUpdates = false
/// Whether to save state.
public static var saveState = true
/// The application identifier. /// The application identifier.
static var appID: String? static var appID: String?
/// The functions handling view updates. /// The functions handling view updates.
static var updateHandlers: [(Bool) -> Void] = [] static var updateHandlers: [(Bool) -> Void] = []
/// The state.
static var state: [State] = []
/// Information about a piece of state.
struct State {
/// The state's identifier.
var id: String
/// Old identifiers of the state which need to be saved.
var oldIDs: [String] = []
/// The state value.
var value: Any?
/// Whether to update in the next iteration.
var update = false
/// Whether the state's identifiers contain a certain identifier.
/// - Parameter id: The identifier.
/// - Returns: Whether the id is contained.
func contains(id: String) -> Bool {
id == self.id || oldIDs.contains(id)
}
/// Change the identifier to a new one.
/// - Parameter newID: The new identifier.
mutating func changeID(new newID: String) {
id = newID
}
}
/// Update all of the views. /// Update all of the views.
/// - Parameter force: Whether to force all views to update. /// - Parameter force: Whether to force all views to update.
@ -57,9 +26,6 @@ public enum StateManager {
for handler in updateHandlers { for handler in updateHandlers {
handler(force) handler(force)
} }
for state in state where state.update {
updatedState(id: state.id)
}
} }
} }
@ -69,64 +35,4 @@ public enum StateManager {
updateHandlers.append(handler) updateHandlers.append(handler)
} }
/// Set the state value for a certain ID.
/// - Parameters:
/// - id: The identifier.
/// - value: The new value.
static func setState(id: String, value: Any?) {
if saveState {
guard let index = state.firstIndex(where: { $0.contains(id: id) }) else {
state.append(.init(id: id, value: value))
return
}
state[safe: index]?.value = value
}
}
/// Get the state value for a certain ID.
/// - Parameter id: The identifier.
/// - Returns: The value.
static func getState(id: String) -> Any? {
state[safe: state.firstIndex { $0.contains(id: id) }]?.value
}
/// Mark the state of a certain id as updated.
/// - Parameter id: The identifier.
static func updateState(id: String) {
if saveState {
state[safe: state.firstIndex { $0.contains(id: id) }]?.update = true
}
}
/// Mark the state of a certain id as not updated.
/// - Parameter id: The identifier.
static func updatedState(id: String) {
if saveState {
state[safe: state.firstIndex { $0.contains(id: id) }]?.update = false
}
}
/// Get whether to update the state of a certain id.
/// - Parameter id: The identifier.
/// - Returns: Whether to update the state.
static func getUpdateState(id: String) -> Bool {
state[safe: state.firstIndex { $0.contains(id: id) }]?.update ?? false
}
/// Change the identifier for a certain state value.
/// - Parameters:
/// - oldID: The old identifier.
/// - newID: The new identifier.
static func changeID(old oldID: String, new newID: String) {
if saveState {
state[safe: state.firstIndex { $0.contains(id: oldID) }]?.changeID(new: newID)
}
}
/// Save a state's identifier until the program ends.
/// - Parameter id: The identifier.
static func addConstantID(_ id: String) {
state[safe: state.firstIndex { $0.id == id }]?.oldIDs.append(id)
}
} }

View File

@ -10,7 +10,7 @@ import Foundation
/// An interface for accessing `State` without specifying the generic type. /// An interface for accessing `State` without specifying the generic type.
protocol StateProtocol { protocol StateProtocol {
/// The identifier for the state property's value. /// The state content.
var id: String { get set } var content: StateContent { get }
} }

View File

@ -62,16 +62,7 @@ extension App {
appInstance.app = Storage(id: appInstance.id) appInstance.app = Storage(id: appInstance.id)
appInstance.app.storage.app = { appInstance } appInstance.app.storage.app = { appInstance }
StateManager.addUpdateHandler { force in StateManager.addUpdateHandler { force in
var updateProperties = force let updateProperties = force || appInstance.getState().contains { $0.value.content.update }
for property in appInstance.getState() {
if let oldID = appInstance.app.storage.stateStorage[property.key]?.id {
StateManager.changeID(old: oldID, new: property.value.id)
appInstance.app.storage.stateStorage[property.key]?.id = property.value.id
}
if StateManager.getUpdateState(id: property.value.id) {
updateProperties = true
}
}
var removeIndices: [Int] = [] var removeIndices: [Int] = []
for (index, element) in appInstance.app.storage.sceneStorage.enumerated() { for (index, element) in appInstance.app.storage.sceneStorage.enumerated() {
if element.destroy { if element.destroy {

View File

@ -43,12 +43,12 @@ struct StateWrapper: ConvenienceWidget {
) where Data: ViewRenderData { ) where Data: ViewRenderData {
var updateProperties = updateProperties var updateProperties = updateProperties
for property in state { for property in state {
if let oldID = storage.state[property.key]?.id { if let storage = storage.state[property.key]?.content.storage {
StateManager.changeID(old: oldID, new: property.value.id) property.value.content.storage = storage
storage.state[property.key]?.id = property.value.id
} }
if StateManager.getUpdateState(id: property.value.id) { if property.value.content.update {
updateProperties = true updateProperties = true
property.value.content.update = false
} }
} }
guard let storage = storage.content[.mainContent]?.first else { guard let storage = storage.content[.mainContent]?.first else {