Automatic saving of changes in some user interface component to a file should be handled differently when you employ ReSwift.
In short, you have to extract the state information from the “save file” action and store it in the overall app’s state somehow. I append these to a collection of PendingFileChanges, a ReSwift.StateType that is part of my overall app state.

“Save file” has the state info of “which file” and “what contents”. In order to enqueue multiple changes to the same file, add a UUID to each file change to identify the event:
struct FileContentChange {
let url: URL
let content: String
let uuid: UUID
}Instances of this type are enqueued with the PendingFileChanges substate. (I used real queues first and relied on the order, but switched to simple sets so I can find and remove elements by UUID easily. You need to make it Hashable to work with sets, though.)
struct AppState: StateType {
var pendingFileChanges: PendingFileChangesState = .empty
}
struct PendingFileChangesState: StateType {
static var empty: PendingFileChangesState {
return PendingFileChangesState(fileContentChanges: [])
}
public fileprivate(set) var fileContentChanges: Set<FileContentChange>
public var nextFileContentChange: FileContentChange? {
return fileContentChanges.first
}
public mutating func insert(fileContentChange: FileContentChange) {
fileContentChanges.insert(fileContentChange)
}
public mutating func remove(fileContentChange: FileContentChange) {
fileContentChanges.remove(fileContentChange)
}
}Some long-running service object will listen to changes to this substate and perform the necessary writes periodically.
To simplify the setup, make the resulting ChangeFileContents service object a ReSwift.StoreSubscriber; now it’ll receive updates to the app state from your store. Cache the last received (and thus pending) change in the service so you can make the operation asynchronous and allow more incoming state updates without issuing to overwrite the file time and again with the same stuff.
When it finishes a write operation, it in turn dispatches an action so the fitting item is removed from the queue.
Here’s a sample service wireframe:
typealias DefaultStore = Store<AppState>
class WriteFileChanges: StoreSubscriber {
let store: DefaultStore
public init(store: DefaultStore) {
self.store = store
}
fileprivate(set) var lastChange: FileContentChange?
public func newState(state: PendingFileChangesState) {
// If the state update shows the queue is empty,
// reset the cache.
guard let change = state.nextFileContentChange else {
lastChange = nil
return
}
// Skip identical, unprocessed updates.
guard change.uuid != lastChange.uuid else { return }
// Set the cached value to prevent duplicate changes.
lastChange = change
delegatePerformingTheFileWriteOperation() { success in
if !success {
// TODO: handle error :)
}
store.dispatch(CompletingFileContentChange(change))
}
}
fileprivate func delegatePerformingTheFileWriteOperation(
completion: (Bool) -> Void) {
// Use FileManager or String.write(toURL:) etc.
completion(true)
}
}So you end up with WriteFileChanges as a service that processes a part of the PendingFileChangesState queue. To let the app know when it has finished, i.e. let a reducer remove the entry from the pending changes queue, it dispatches a CompletingFileContentChange action. I leave this simple wrapper as an exercise to the reader.
via Worklog of Christian Tietze http://ift.tt/2luYfgg
