PersistedProperty.swift 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import Foundation
  2. /// Attention! Do not use this wrapper for mutating structure with `didSet` handler into property owner!
  3. /// `didSet` will never called if structure mutate into itself (by "mutating functions").
  4. @propertyWrapper struct Persisted<Value: Codable & Equatable> {
  5. var wrappedValue: Value {
  6. get { getValue() ?? initialValue }
  7. set { setValue(newValue) }
  8. }
  9. private func getValue() -> Value? {
  10. lock?.lock()
  11. defer { lock?.unlock() }
  12. return storage.getValue(Value.self, forKey: key)
  13. }
  14. private mutating func setValue(_ value: Value) {
  15. lock?.lock()
  16. defer { lock?.unlock() }
  17. storage.setValue(value, forKey: key)
  18. }
  19. private let key: String
  20. private let storage: KeyValueStorage
  21. private let lock: NSRecursiveLock?
  22. private let initialValue: Value
  23. var isInitialValue: Bool {
  24. if let value = getValue() {
  25. return value == initialValue
  26. }
  27. return true
  28. }
  29. init(
  30. wrappedValue: Value,
  31. key: String,
  32. storage: KeyValueStorage = UserDefaults.standard,
  33. lock: NSRecursiveLock? = nil
  34. ) {
  35. self.storage = storage
  36. self.key = key
  37. self.lock = lock
  38. initialValue = wrappedValue
  39. lock?.lock()
  40. defer { lock?.unlock() }
  41. if storage.getValue(Value.self, forKey: key) == nil {
  42. setValue(wrappedValue)
  43. }
  44. }
  45. }
  46. @propertyWrapper public struct PersistedProperty<Value> {
  47. let key: String
  48. let storageURL: URL
  49. public init(key: String) {
  50. self.key = key
  51. let documents: URL
  52. guard let localDocuments = try? FileManager.default.url(
  53. for: .documentDirectory,
  54. in: .userDomainMask,
  55. appropriateFor: nil,
  56. create: true
  57. ) else {
  58. preconditionFailure("Could not get a documents directory URL.")
  59. }
  60. documents = localDocuments
  61. storageURL = documents.appendingPathComponent(key + ".plist")
  62. }
  63. public var wrappedValue: Value? {
  64. get {
  65. do {
  66. let data = try Data(contentsOf: storageURL)
  67. guard let value = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? Value
  68. else {
  69. return nil
  70. }
  71. return value
  72. } catch {}
  73. return nil
  74. }
  75. set {
  76. guard let newValue = newValue else {
  77. do {
  78. try FileManager.default.removeItem(at: storageURL)
  79. } catch {}
  80. return
  81. }
  82. do {
  83. let data = try PropertyListSerialization.data(fromPropertyList: newValue, format: .binary, options: 0)
  84. try data.write(to: storageURL, options: .atomic)
  85. } catch {}
  86. }
  87. }
  88. }