Skip to content

Commit

Permalink
Shifted some stuff around
Browse files Browse the repository at this point in the history
  • Loading branch information
KaiTheRedNinja committed Sep 10, 2023
1 parent 063c3fe commit ad4433d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 86 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,7 @@ binding to the persistent using the dollar sign prefix:
Declare ``Persistent`` as private to prevent setting it in a memberwise initializer.
Use persistent for storage that's local to a view and its subviews and requires it to be
persisted.

## Implementation Details
SwiftPersistence uses the FileSystem class from
[The GlassRoom](https://github.com/KaiTheRedNinja/The-GlassRoom) to manage saving to the file system
29 changes: 2 additions & 27 deletions Sources/SwiftPersistence/FileSystem.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//
// FileSystem.swift
//
//
// Created by Tristan Chay on 8/9/23.
// Created by Kai Quan Tay on 13/5/23 for The GlassRoom.
// Transferred by Tristan Chay on 8/9/23.
//

import Foundation
Expand Down Expand Up @@ -93,28 +93,3 @@ public extension URL {
return attributes?[.creationDate] as? Date
}
}

public extension Array {
func mergedWith(other: [Element],
isSame: (Element, Element) -> Bool,
isBefore: (Element, Element) -> Bool) -> [Element] {
let mergedArray = self + other
let sortedArray = mergedArray.sorted(by: isBefore)
var result: [Element] = []

for element in sortedArray {
if !result.contains(where: { isSame($0, element) }) {
result.append(element)
}
}

return result
}

mutating func mergeWith(other: [Element],
isSame: (Element, Element) -> Bool,
isBefore: (Element, Element) -> Bool) {
self = mergedWith(other: other, isSame: isSame, isBefore: isBefore)
}
}

122 changes: 63 additions & 59 deletions Sources/SwiftPersistence/SwiftPersistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import SwiftUI

var writeQueue = DispatchQueue(label: "SwiftPersistence.writeQueue")

/// Persistent is a property wrapper type that can read and write a persisted value saved in FileManager.
///
/// Use persistent as the single source of truth for a given value type that you
Expand Down Expand Up @@ -109,93 +111,95 @@ import SwiftUI
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen @propertyWrapper public struct Persistent<Value: Codable> : DynamicProperty {

@State private var value: Value
@State private var value: Value?
private var defaultValue: Value
private var internalFilename: String
private var internalStorageMethod: SwiftPersistenceStorage

/// The method chosen to store the encoded JSON String.
public enum SwiftPersistenceStorage {
case fileManager

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
case appStorage

/// A property wrapper type that reflects a value from FileManager and works with all Codable types.
/// - Parameters:
/// - filename: The name of the file you want FileManager to save to.
/// - defaultValue: The default value of the variable upon first initialisation of its type.
public init(
wrappedValue defaultValue: Value,
_ fileName: String,
store: SwiftPersistenceStorage = .fileManager
) {
self.defaultValue = defaultValue
self.internalFilename = fileName
self.internalStorageMethod = store

self.value = nil
}

func getValueFromStorage() -> Value? {
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *), internalStorageMethod == .appStorage {
@AppStorage(internalFilename) var valueStoredInFilename = Data()
do {
let decodedData = try JSONDecoder().decode(Value.self, from: valueStoredInFilename)
return decodedData
} catch {}
} else if internalStorageMethod == .fileManager,
let result = FileSystem.read(Value.self, from: internalFilename) {
return result
}
return nil
}

/// The underlying value referenced by the binding variable.
public var wrappedValue: Value {
get { value }
get {
value ?? getValueFromStorage() ?? defaultValue
}
nonmutating set {
switch internalStorageMethod {
case .fileManager:
fileSystemRead(newValue)
case .appStorage:
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
appStorageRead(newValue)
} else {
fileSystemRead(newValue)
value = newValue

writeQueue.async {
switch internalStorageMethod {
case .fileManager:
fileSystemWrite(newValue)
case .appStorage:
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
appStorageWrite(newValue)
} else {
fileSystemWrite(newValue)
}
}
}
}

}

/// A projection of the binding value that returns a binding.
public var projectedValue: Binding<Value> {
Binding(
get: { wrappedValue },
set: { wrappedValue = $0 }
)
}

/// A property wrapper type that reflects a value from FileManager and works with all Codable types.
/// - Parameters:
/// - filename: The name of the file you want FileManager to save to.
/// - defaultValue: The default value of the variable upon first initialisation of its type.
public init(wrappedValue defaultValue: Value, _ fileName: String, store: SwiftPersistenceStorage = SwiftPersistenceStorage.fileManager) {
internalFilename = fileName
internalStorageMethod = store

switch store {
case .fileManager:
if let result = FileSystem.read(Value.self, from: fileName) {
_value = State(wrappedValue: result)
} else {
_value = State(wrappedValue: defaultValue)
}
case .appStorage:
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
@AppStorage(fileName) var valueStoredInFilename = Data()
do {
let decodedData = try JSONDecoder().decode(Value.self, from: valueStoredInFilename)
_value = State(wrappedValue: decodedData)
} catch {
print("Error occurred while attempting to decode from AppStorage")
_value = State(wrappedValue: defaultValue)
}
} else {
// Fallback to fileManager
if let result = FileSystem.read(Value.self, from: fileName) {
_value = State(wrappedValue: result)
} else {
_value = State(wrappedValue: defaultValue)
}
}
}
}

private func fileSystemRead(_ newValue: Value) {

private func fileSystemWrite(_ newValue: Value) {
FileSystem.write(newValue, to: internalFilename)
value = newValue
}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
private func appStorageRead(_ newValue: Value) {
private func appStorageWrite(_ newValue: Value) {
@AppStorage(internalFilename) var valueStoredInFilename = Data()
do {
let encodedNewValue = try JSONEncoder().encode(newValue)
valueStoredInFilename = encodedNewValue
value = newValue
} catch {
print("Error occurred while attempting to encode to AppStorage")
}
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension Persistent {
/// The method chosen to store the encoded JSON String.
enum SwiftPersistenceStorage {
case fileManager

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
case appStorage
}
}

0 comments on commit ad4433d

Please sign in to comment.