Skip to content

Commit 174b791

Browse files
Disable syncing from local if its default after iCloud.add (#185)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent bf71746 commit 174b791

File tree

5 files changed

+72
-21
lines changed

5 files changed

+72
-21
lines changed

Sources/Defaults/Defaults+iCloud.swift

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ extension Defaults {
3939

4040
## Dynamically Toggle Syncing
4141

42-
You can also toggle the syncing behavior dynamically using the ``Defaults/iCloud/add(_:)-5gffb`` and ``Defaults/iCloud/remove(_:)-1b8w5`` methods.
42+
You can also toggle the syncing behavior dynamically using the ``Defaults/iCloud/add(_:)`` and ``Defaults/iCloud/remove(_:)-1b8w5`` methods.
4343

4444
```swift
4545
import Defaults
@@ -82,15 +82,10 @@ extension Defaults {
8282
/**
8383
Add the keys to be automatically synced.
8484
*/
85-
public static func add(_ keys: Defaults.Keys...) {
86-
synchronizer.add(keys)
87-
}
88-
89-
/**
90-
Add the keys to be automatically synced.
91-
*/
92-
public static func add(_ keys: [Defaults.Keys]) {
93-
synchronizer.add(keys)
85+
// TODO: Support array of Defaults.Key after Swift 6 pack iteration is supported.
86+
// https://github.com/sindresorhus/Defaults/pull/185#discussion_r1704464183
87+
public static func add<each Value: Defaults.Serializable>(_ keys: repeat Defaults.Key<each Value>) {
88+
repeat synchronizer.add(each keys)
9489
}
9590

9691
/**
@@ -269,11 +264,23 @@ final class iCloudSynchronizer {
269264
/**
270265
Add new key and start to observe its changes.
271266
*/
272-
func add(_ keys: [Defaults.Keys]) {
273-
self.keys.formUnion(keys)
274-
syncWithoutWaiting(keys)
275-
for key in keys {
276-
localKeysMonitor.add(key: key)
267+
func add<Value: Defaults.Serializable>(_ key: Defaults.Key<Value>) {
268+
let (isInserted, _) = self.keys.insert(key)
269+
guard isInserted else {
270+
return
271+
}
272+
273+
localKeysMonitor.add(key: key)
274+
275+
// If the local value is the default value, only sync from remote, since all devices should already have the default value.
276+
if key._isDefaultValue {
277+
guard case .remote = latestDataSource(forKey: key) else {
278+
return
279+
}
280+
281+
syncWithoutWaiting([key], .remote)
282+
} else {
283+
syncWithoutWaiting([key])
277284
}
278285
}
279286

Sources/Defaults/Defaults.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,31 @@ extension Defaults.Key {
187187
) where Value == T? {
188188
self.init(name, default: nil, suite: suite, iCloud: iCloud)
189189
}
190+
191+
/**
192+
Check whether the stored value is the default value.
193+
194+
- Note: This is only for internal use because it would not work for non-equatable values.
195+
*/
196+
var _isDefaultValue: Bool {
197+
let defaultValue = defaultValue
198+
let value = suite[self]
199+
guard
200+
let defaultValue = defaultValue as? any Equatable,
201+
let value = value as? any Equatable
202+
else {
203+
return false
204+
}
205+
206+
return defaultValue.isEqual(value)
207+
}
208+
}
209+
210+
extension Defaults.Key where Value: Equatable {
211+
/**
212+
Check whether the stored value is the default value.
213+
*/
214+
public var isDefaultValue: Bool { self._isDefaultValue }
190215
}
191216

192217
extension Defaults {

Sources/Defaults/Utilities.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ extension Collection {
165165
}
166166
}
167167

168+
extension Equatable {
169+
func isEqual(_ rhs: any Equatable) -> Bool {
170+
guard
171+
let rhs = rhs as? Self,
172+
rhs == self
173+
else {
174+
return false
175+
}
176+
177+
return true
178+
}
179+
}
180+
168181
extension Defaults {
169182
@usableFromInline
170183
static func isValidKeyPath(name: String) -> Bool {

Tests/DefaultsTests/Defaults+iCloudTests.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,12 @@ final class DefaultsICloudTests: XCTestCase {
8888
}
8989

9090
func testICloudInitialize() async {
91-
print(Defaults.iCloud.keys)
9291
let name = Defaults.Key<String>("testICloudInitialize_name", default: "0", iCloud: true)
9392
let quality = Defaults.Key<Double>("testICloudInitialize_quality", default: 0.0, iCloud: true)
9493

95-
print(Defaults.iCloud.keys)
9694
await Defaults.iCloud.waitForSyncCompletion()
97-
XCTAssertEqual(mockStorage.data(forKey: name.name), "0")
98-
XCTAssertEqual(mockStorage.data(forKey: quality.name), 0.0)
95+
XCTAssertNil(mockStorage.data(forKey: name.name))
96+
XCTAssertNil(mockStorage.data(forKey: quality.name))
9997
let name_expected = ["1", "2", "3", "4", "5", "6", "7"]
10098
let quality_expected = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
10199

@@ -251,8 +249,9 @@ final class DefaultsICloudTests: XCTestCase {
251249

252250
func testAddFromDetached() async {
253251
let name = Defaults.Key<String>("testInitAddFromDetached_name", default: "0")
252+
let quantity = Defaults.Key<Bool>("testInitAddFromDetached_quantity", default: false)
254253
let task = Task.detached {
255-
Defaults.iCloud.add(name)
254+
Defaults.iCloud.add(name, quantity)
256255
Defaults.iCloud.syncWithoutWaiting()
257256
await Defaults.iCloud.waitForSyncCompletion()
258257
}
@@ -268,7 +267,7 @@ final class DefaultsICloudTests: XCTestCase {
268267
let name = Defaults.Key<String>("testICloudInitializeFromDetached_name", default: "0", iCloud: true)
269268

270269
await Defaults.iCloud.waitForSyncCompletion()
271-
XCTAssertEqual(mockStorage.data(forKey: name.name), "0")
270+
XCTAssertNil(mockStorage.data(forKey: name.name))
272271
}
273272
await task.value
274273
}

Tests/DefaultsTests/DefaultsTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ final class DefaultsTests: XCTestCase {
177177
Defaults.removeAll(suite: customSuite)
178178
}
179179

180+
func testIsDefaultValue() {
181+
let key = Defaults.Key<Bool>("isDefaultValue", default: false)
182+
XCTAssert(key.isDefaultValue)
183+
Defaults[key].toggle()
184+
XCTAssert(!key.isDefaultValue)
185+
}
186+
180187
func testObserveKeyCombine() {
181188
let key = Defaults.Key<Bool>("observeKey", default: false)
182189
let expect = expectation(description: "Observation closure being called")

0 commit comments

Comments
 (0)