Switch to state management using reference types #1
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user