Skip to content

Commit 27079ac

Browse files
committed
Property wrapper now works in a ObservableObject 🎉
1 parent 8ca1149 commit 27079ac

File tree

1 file changed

+49
-15
lines changed

1 file changed

+49
-15
lines changed

Sources/SecureStorage/SecureStorage.swift

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Foundation
1414
import SwiftUI
1515
import Security
16+
import Combine
1617

1718
/// A property wrapper that reads and writes to the keychain.
1819
///
@@ -29,9 +30,13 @@ public struct SecureStorage: DynamicProperty {
2930
var service: String
3031

3132
/// Should delete when asked?
32-
var shouldDeleteWhenAsked: Bool = false
33+
var isInitialized: Bool = false
3334

34-
@State var value: String?
35+
/// The cancellables to store.
36+
var cancellables = Set<AnyCancellable>()
37+
38+
/// The observed storage
39+
@ObservedObject private var store: Storage
3540

3641
/// Creates an `SecureStorage` property.
3742
///
@@ -44,8 +49,6 @@ public struct SecureStorage: DynamicProperty {
4449
) {
4550
self.key = key
4651
self.service = service
47-
self.wrappedValue = wrappedValue
48-
self.shouldDeleteWhenAsked = true
4952

5053
let query = [
5154
kSecAttrService: service,
@@ -59,19 +62,20 @@ public struct SecureStorage: DynamicProperty {
5962

6063
if let data = result as? Data,
6164
let string = String(data: data, encoding: .utf8) {
62-
self.value = string
65+
store = .init(string)
6366
} else {
64-
self.value = nil
67+
store = .init(nil)
6568
}
69+
70+
self.isInitialized = true
6671
}
6772

68-
/// The value of the key in the keychain.
73+
/// The value of the key in iCloud.
6974
public var wrappedValue: String? {
7075
get {
71-
return value
76+
return store.value
7277
}
7378

74-
// This needs to be nonmutating because we're setting a property on a struct.
7579
nonmutating set {
7680
if let newValue {
7781
let data = Data(newValue.utf8)
@@ -102,7 +106,7 @@ public struct SecureStorage: DynamicProperty {
102106
}
103107
} else {
104108
// Wait until we are initialized before deleting items
105-
if shouldDeleteWhenAsked {
109+
if isInitialized {
106110
let query = [
107111
kSecAttrService: service,
108112
kSecAttrAccount: key,
@@ -113,16 +117,28 @@ public struct SecureStorage: DynamicProperty {
113117
}
114118
}
115119

116-
value = newValue
120+
store.value = newValue
117121
}
118122
}
119123

120124
/// A binding to the value of the key in iCloud.
121125
public var projectedValue: Binding<String?> {
122-
Binding {
123-
return self.wrappedValue
124-
} set: { newValue in
125-
self.wrappedValue = newValue
126+
$store.value
127+
}
128+
129+
// MARK: - Storage
130+
private final class Storage: ObservableObject {
131+
var parentWillChange: ObservableObjectPublisher?
132+
133+
var value: String? {
134+
willSet {
135+
objectWillChange.send()
136+
parentWillChange?.send()
137+
}
138+
}
139+
140+
init(_ value: String?) {
141+
self.value = value
126142
}
127143
}
128144

@@ -146,5 +162,23 @@ public struct SecureStorage: DynamicProperty {
146162

147163
SecItemDelete(query)
148164
}
165+
166+
/// Get the parent, to send a willChange event to there.
167+
public static subscript<OuterSelf: ObservableObject>(
168+
_enclosingInstance instance: OuterSelf,
169+
wrapped wrappedKeyPath: ReferenceWritableKeyPath<OuterSelf, String?>,
170+
storage storageKeyPath: ReferenceWritableKeyPath<OuterSelf, Self>
171+
) -> String? {
172+
get {
173+
instance[keyPath: storageKeyPath].store.parentWillChange = (
174+
instance.objectWillChange as? ObservableObjectPublisher
175+
)
176+
177+
return instance[keyPath: storageKeyPath].wrappedValue
178+
}
179+
set {
180+
instance[keyPath: storageKeyPath].wrappedValue = newValue
181+
}
182+
}
149183
}
150184
#endif

0 commit comments

Comments
 (0)