Disable syncing from local if its default after `iCloud.add` (#185)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
hank121314 2024-08-13 04:50:47 +08:00 committed by GitHub
parent bf717462d9
commit 174b791fec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 21 deletions

View File

@ -39,7 +39,7 @@ extension Defaults {
## Dynamically Toggle Syncing ## Dynamically Toggle Syncing
You can also toggle the syncing behavior dynamically using the ``Defaults/iCloud/add(_:)-5gffb`` and ``Defaults/iCloud/remove(_:)-1b8w5`` methods. You can also toggle the syncing behavior dynamically using the ``Defaults/iCloud/add(_:)`` and ``Defaults/iCloud/remove(_:)-1b8w5`` methods.
```swift ```swift
import Defaults import Defaults
@ -82,15 +82,10 @@ extension Defaults {
/** /**
Add the keys to be automatically synced. Add the keys to be automatically synced.
*/ */
public static func add(_ keys: Defaults.Keys...) { // TODO: Support array of Defaults.Key after Swift 6 pack iteration is supported.
synchronizer.add(keys) // https://github.com/sindresorhus/Defaults/pull/185#discussion_r1704464183
} public static func add<each Value: Defaults.Serializable>(_ keys: repeat Defaults.Key<each Value>) {
repeat synchronizer.add(each keys)
/**
Add the keys to be automatically synced.
*/
public static func add(_ keys: [Defaults.Keys]) {
synchronizer.add(keys)
} }
/** /**
@ -269,11 +264,23 @@ final class iCloudSynchronizer {
/** /**
Add new key and start to observe its changes. Add new key and start to observe its changes.
*/ */
func add(_ keys: [Defaults.Keys]) { func add<Value: Defaults.Serializable>(_ key: Defaults.Key<Value>) {
self.keys.formUnion(keys) let (isInserted, _) = self.keys.insert(key)
syncWithoutWaiting(keys) guard isInserted else {
for key in keys { return
}
localKeysMonitor.add(key: key) localKeysMonitor.add(key: key)
// If the local value is the default value, only sync from remote, since all devices should already have the default value.
if key._isDefaultValue {
guard case .remote = latestDataSource(forKey: key) else {
return
}
syncWithoutWaiting([key], .remote)
} else {
syncWithoutWaiting([key])
} }
} }

View File

@ -187,6 +187,31 @@ extension Defaults.Key {
) where Value == T? { ) where Value == T? {
self.init(name, default: nil, suite: suite, iCloud: iCloud) self.init(name, default: nil, suite: suite, iCloud: iCloud)
} }
/**
Check whether the stored value is the default value.
- Note: This is only for internal use because it would not work for non-equatable values.
*/
var _isDefaultValue: Bool {
let defaultValue = defaultValue
let value = suite[self]
guard
let defaultValue = defaultValue as? any Equatable,
let value = value as? any Equatable
else {
return false
}
return defaultValue.isEqual(value)
}
}
extension Defaults.Key where Value: Equatable {
/**
Check whether the stored value is the default value.
*/
public var isDefaultValue: Bool { self._isDefaultValue }
} }
extension Defaults { extension Defaults {

View File

@ -165,6 +165,19 @@ extension Collection {
} }
} }
extension Equatable {
func isEqual(_ rhs: any Equatable) -> Bool {
guard
let rhs = rhs as? Self,
rhs == self
else {
return false
}
return true
}
}
extension Defaults { extension Defaults {
@usableFromInline @usableFromInline
static func isValidKeyPath(name: String) -> Bool { static func isValidKeyPath(name: String) -> Bool {

View File

@ -88,14 +88,12 @@ final class DefaultsICloudTests: XCTestCase {
} }
func testICloudInitialize() async { func testICloudInitialize() async {
print(Defaults.iCloud.keys)
let name = Defaults.Key<String>("testICloudInitialize_name", default: "0", iCloud: true) let name = Defaults.Key<String>("testICloudInitialize_name", default: "0", iCloud: true)
let quality = Defaults.Key<Double>("testICloudInitialize_quality", default: 0.0, iCloud: true) let quality = Defaults.Key<Double>("testICloudInitialize_quality", default: 0.0, iCloud: true)
print(Defaults.iCloud.keys)
await Defaults.iCloud.waitForSyncCompletion() await Defaults.iCloud.waitForSyncCompletion()
XCTAssertEqual(mockStorage.data(forKey: name.name), "0") XCTAssertNil(mockStorage.data(forKey: name.name))
XCTAssertEqual(mockStorage.data(forKey: quality.name), 0.0) XCTAssertNil(mockStorage.data(forKey: quality.name))
let name_expected = ["1", "2", "3", "4", "5", "6", "7"] let name_expected = ["1", "2", "3", "4", "5", "6", "7"]
let quality_expected = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] let quality_expected = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
@ -251,8 +249,9 @@ final class DefaultsICloudTests: XCTestCase {
func testAddFromDetached() async { func testAddFromDetached() async {
let name = Defaults.Key<String>("testInitAddFromDetached_name", default: "0") let name = Defaults.Key<String>("testInitAddFromDetached_name", default: "0")
let quantity = Defaults.Key<Bool>("testInitAddFromDetached_quantity", default: false)
let task = Task.detached { let task = Task.detached {
Defaults.iCloud.add(name) Defaults.iCloud.add(name, quantity)
Defaults.iCloud.syncWithoutWaiting() Defaults.iCloud.syncWithoutWaiting()
await Defaults.iCloud.waitForSyncCompletion() await Defaults.iCloud.waitForSyncCompletion()
} }
@ -268,7 +267,7 @@ final class DefaultsICloudTests: XCTestCase {
let name = Defaults.Key<String>("testICloudInitializeFromDetached_name", default: "0", iCloud: true) let name = Defaults.Key<String>("testICloudInitializeFromDetached_name", default: "0", iCloud: true)
await Defaults.iCloud.waitForSyncCompletion() await Defaults.iCloud.waitForSyncCompletion()
XCTAssertEqual(mockStorage.data(forKey: name.name), "0") XCTAssertNil(mockStorage.data(forKey: name.name))
} }
await task.value await task.value
} }

View File

@ -177,6 +177,13 @@ final class DefaultsTests: XCTestCase {
Defaults.removeAll(suite: customSuite) Defaults.removeAll(suite: customSuite)
} }
func testIsDefaultValue() {
let key = Defaults.Key<Bool>("isDefaultValue", default: false)
XCTAssert(key.isDefaultValue)
Defaults[key].toggle()
XCTAssert(!key.isDefaultValue)
}
func testObserveKeyCombine() { func testObserveKeyCombine() {
let key = Defaults.Key<Bool>("observeKey", default: false) let key = Defaults.Key<Bool>("observeKey", default: false)
let expect = expectation(description: "Observation closure being called") let expect = expectation(description: "Observation closure being called")