13
13
import Foundation
14
14
import SwiftUI
15
15
import Security
16
+ import Combine
16
17
17
18
/// A property wrapper that reads and writes to the keychain.
18
19
///
@@ -29,9 +30,13 @@ public struct SecureStorage: DynamicProperty {
29
30
var service : String
30
31
31
32
/// Should delete when asked?
32
- var shouldDeleteWhenAsked : Bool = false
33
+ var isInitialized : Bool = false
33
34
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
35
40
36
41
/// Creates an `SecureStorage` property.
37
42
///
@@ -44,8 +49,6 @@ public struct SecureStorage: DynamicProperty {
44
49
) {
45
50
self . key = key
46
51
self . service = service
47
- self . wrappedValue = wrappedValue
48
- self . shouldDeleteWhenAsked = true
49
52
50
53
let query = [
51
54
kSecAttrService: service,
@@ -59,19 +62,20 @@ public struct SecureStorage: DynamicProperty {
59
62
60
63
if let data = result as? Data ,
61
64
let string = String ( data: data, encoding: . utf8) {
62
- self . value = string
65
+ store = . init ( string)
63
66
} else {
64
- self . value = nil
67
+ store = . init ( nil )
65
68
}
69
+
70
+ self . isInitialized = true
66
71
}
67
72
68
- /// The value of the key in the keychain .
73
+ /// The value of the key in iCloud .
69
74
public var wrappedValue : String ? {
70
75
get {
71
- return value
76
+ return store . value
72
77
}
73
78
74
- // This needs to be nonmutating because we're setting a property on a struct.
75
79
nonmutating set {
76
80
if let newValue {
77
81
let data = Data ( newValue. utf8)
@@ -102,7 +106,7 @@ public struct SecureStorage: DynamicProperty {
102
106
}
103
107
} else {
104
108
// Wait until we are initialized before deleting items
105
- if shouldDeleteWhenAsked {
109
+ if isInitialized {
106
110
let query = [
107
111
kSecAttrService: service,
108
112
kSecAttrAccount: key,
@@ -113,16 +117,28 @@ public struct SecureStorage: DynamicProperty {
113
117
}
114
118
}
115
119
116
- value = newValue
120
+ store . value = newValue
117
121
}
118
122
}
119
123
120
124
/// A binding to the value of the key in iCloud.
121
125
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
126
142
}
127
143
}
128
144
@@ -146,5 +162,23 @@ public struct SecureStorage: DynamicProperty {
146
162
147
163
SecItemDelete ( query)
148
164
}
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
+ }
149
183
}
150
184
#endif
0 commit comments