Add a `Defaults.Serializable` protocol (#57)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
hank121314 2021-05-16 20:21:17 +08:00 committed by Sindre Sorhus
parent f64b3ca562
commit 534157f1b5
37 changed files with 7539 additions and 782 deletions

View File

@ -5,9 +5,20 @@ on:
jobs:
test:
runs-on: macos-latest
strategy:
matrix:
scheme: [Defaults-macOS, Defaults-iOS, Defaults-tvOS]
include:
- scheme: Defaults-macOS
destination: macOS
- scheme: Defaults-iOS
destination: iOS Simulator,name=iPhone 8
- scheme: Defaults-tvOS
destination: tvOS Simulator,name=Apple TV
steps:
- uses: actions/checkout@v2
- run: swift test
- name: Run tests
run: xcodebuild clean test -project Defaults.xcodeproj -scheme ${{ matrix.scheme }} -destination "platform=${{ matrix.destination }}" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
lint:
runs-on: ubuntu-latest
steps:

View File

@ -8,6 +8,79 @@
/* Begin PBXBuildFile section */
52D6D9871BEFF229002C0205 /* Defaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Defaults.framework */; };
71056FF425DE6DEF00524EDA /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
71056FF525DE6DEF00524EDA /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
71056FF625DE6DEF00524EDA /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
7108EAC125942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */; };
7108EAC225942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */; };
7108EAC325942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */; };
7108EAF425949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */; };
7108EAF525949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */; };
7108EAF625949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */; };
71362AC025A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */; };
71362AC125A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */; };
71362AC225A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */; };
71362ACB25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */; };
71362ACC25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */; };
71362ACD25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */; };
7150D6F425C2968700201966 /* DefaultsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */; };
7150D6F525C2968700201966 /* DefaultsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */; };
7150D6F625C2968700201966 /* DefaultsMigrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */; };
7152A04D25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */; };
7152A04E25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */; };
7152A04F25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */; };
71573E9925A445CE00F18D4E /* DefaultsCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */; };
71573EA125A445D400F18D4E /* DefaultsCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */; };
71573EA925A445DB00F18D4E /* DefaultsCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */; };
7168638325E886DB00F55131 /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
718B783325917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
718B783425917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
718B783525917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
718B783625917CCA004FF90D /* Defaults+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783225917CCA004FF90D /* Defaults+Protocol.swift */; };
718B783F25917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
718B784025917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
718B784125917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
718B784225917D09004FF90D /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718B783E25917D09004FF90D /* Defaults+Extensions.swift */; };
7191AD872591977700AD472F /* DefaultsArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191AD862591977700AD472F /* DefaultsArrayTests.swift */; };
7191AD882591977700AD472F /* DefaultsArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191AD862591977700AD472F /* DefaultsArrayTests.swift */; };
7191AD892591977700AD472F /* DefaultsArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191AD862591977700AD472F /* DefaultsArrayTests.swift */; };
719F5E20258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
719F5E21258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
719F5E22258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
719F5E23258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */; };
71A7132D260497EE004095EE /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
71A7132E260497EE004095EE /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
71A7132F260497EE004095EE /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
71A71330260497EE004095EE /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
71A713482604A084004095EE /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
71A713492604A084004095EE /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
71A7134A2604A084004095EE /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
71A7134B2604A084004095EE /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
71A7135B2604A13B004095EE /* Migration+Protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */; };
71A7135C2604A13B004095EE /* Migration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */; };
71A7135D2604A13B004095EE /* Migration+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */; };
71A7135E2604A13B004095EE /* Migration+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7168638225E886DB00F55131 /* Migration+Defaults.swift */; };
71B96F1E259986F100079F69 /* DefaultsCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */; };
71B96F1F259986F100079F69 /* DefaultsCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */; };
71B96F20259986F100079F69 /* DefaultsCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */; };
71C55FF6259C13190053CCB3 /* DefaultsNSColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C55FF4259C13190053CCB3 /* DefaultsNSColorTests.swift */; };
71C56007259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */; };
71C56009259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */; };
71C5602E259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */; };
71C5602F259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */; };
71C56030259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */; };
71F002F925959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */; };
71F002FA25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */; };
71F002FB25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */; };
71F003042595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */; };
71F003052595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */; };
71F003062595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */; };
71F0030F2595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */; };
71F003102595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */; };
71F003112595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */; };
71F0031A2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */; };
71F0031B2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */; };
71F0031C2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */; };
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
@ -74,6 +147,29 @@
52D6D9F01BEFFFBE002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
52D6DA0F1BF000BD002C0205 /* Defaults.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Defaults.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6614F6E222FC6E1C00B0C9CE /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = readme.md; sourceTree = "<group>"; usesTabs = 1; };
71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Migration+Protocol.swift"; sourceTree = "<group>"; };
71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Migration+Extensions.swift"; sourceTree = "<group>"; };
71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Migration+UserDefaults.swift"; sourceTree = "<group>"; };
7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSwiftUITests.swift; sourceTree = "<group>"; };
7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsDictionaryTests.swift; sourceTree = "<group>"; };
71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCollectionCustomElementTests.swift; sourceTree = "<group>"; };
71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSetAlgebraTests.swift; sourceTree = "<group>"; };
7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsMigrationTests.swift; sourceTree = "<group>"; };
7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSetAlgebraCustomElementTests.swift; sourceTree = "<group>"; };
71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCollectionTests.swift; sourceTree = "<group>"; };
7168638225E886DB00F55131 /* Migration+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Migration+Defaults.swift"; sourceTree = "<group>"; };
718B783225917CCA004FF90D /* Defaults+Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Defaults+Protocol.swift"; sourceTree = "<group>"; };
718B783E25917D09004FF90D /* Defaults+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Defaults+Extensions.swift"; sourceTree = "<group>"; };
7191AD862591977700AD472F /* DefaultsArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsArrayTests.swift; sourceTree = "<group>"; };
719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Defaults+Bridge.swift"; sourceTree = "<group>"; };
71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCodableTests.swift; sourceTree = "<group>"; };
71C55FF4259C13190053CCB3 /* DefaultsNSColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsNSColorTests.swift; sourceTree = "<group>"; };
71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsUIColorTests.swift; sourceTree = "<group>"; };
71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsSetTests.swift; sourceTree = "<group>"; };
71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsNSSecureCodingTests.swift; sourceTree = "<group>"; };
71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCustomBridgeTests.swift; sourceTree = "<group>"; };
71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsEnumTests.swift; sourceTree = "<group>"; };
71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsCodableEnumTests.swift; sourceTree = "<group>"; };
8933C7841EB5B820000D00A4 /* Defaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Defaults.swift; sourceTree = "<group>"; usesTabs = 1; };
8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DefaultsTests.swift; sourceTree = "<group>"; usesTabs = 1; };
AD2FAA261CD0B6D800659CF4 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
@ -179,6 +275,25 @@
path = Configs;
sourceTree = "<group>";
};
71056FF025DE6DEF00524EDA /* Migration */ = {
isa = PBXGroup;
children = (
7156A8F226205E0C00A1A66E /* v5 */,
7168638225E886DB00F55131 /* Migration+Defaults.swift */,
);
path = Migration;
sourceTree = "<group>";
};
7156A8F226205E0C00A1A66E /* v5 */ = {
isa = PBXGroup;
children = (
71056FF125DE6DEF00524EDA /* Migration+Protocol.swift */,
71056FF225DE6DEF00524EDA /* Migration+Extensions.swift */,
71056FF325DE6DEF00524EDA /* Migration+UserDefaults.swift */,
);
path = v5;
sourceTree = "<group>";
};
8933C7811EB5B7E0000D00A4 /* Sources */ = {
isa = PBXGroup;
children = (
@ -191,6 +306,22 @@
isa = PBXGroup;
children = (
8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */,
7191AD862591977700AD472F /* DefaultsArrayTests.swift */,
7108EAC025942BF60013A623 /* DefaultsSwiftUITests.swift */,
7108EAF325949DBF0013A623 /* DefaultsDictionaryTests.swift */,
71F002F825959000001A1864 /* DefaultsNSSecureCodingTests.swift */,
71F003032595B460001A1864 /* DefaultsCustomBridgeTests.swift */,
71F0030E2595C7B8001A1864 /* DefaultsEnumTests.swift */,
71F003192595E15B001A1864 /* DefaultsCodableEnumTests.swift */,
71B96F1D259986F100079F69 /* DefaultsCodableTests.swift */,
71C55FF4259C13190053CCB3 /* DefaultsNSColorTests.swift */,
71C56006259C25080053CCB3 /* DefaultsUIColorTests.swift */,
71C5602D259C2A950053CCB3 /* DefaultsSetTests.swift */,
71573E8925A445A100F18D4E /* DefaultsCollectionTests.swift */,
71362ABF25A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift */,
71362ACA25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift */,
7152A04C25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift */,
7150D6F325C2968700201966 /* DefaultsMigrationTests.swift */,
);
name = Tests;
path = Tests/DefaultsTests;
@ -215,6 +346,7 @@
E30E93D822E9425E00530C8F /* Defaults */ = {
isa = PBXGroup;
children = (
71056FF025DE6DEF00524EDA /* Migration */,
8933C7841EB5B820000D00A4 /* Defaults.swift */,
E339B3B72449F10D00E7A40A /* UserDefaults.swift */,
E339B3B22449ED2000E7A40A /* Reset.swift */,
@ -222,6 +354,9 @@
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */,
E3EB3E32216505920033B089 /* Utilities.swift */,
718B783225917CCA004FF90D /* Defaults+Protocol.swift */,
718B783E25917D09004FF90D /* Defaults+Extensions.swift */,
719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */,
);
path = Defaults;
sourceTree = "<group>";
@ -264,6 +399,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Defaults-iOS" */;
buildPhases = (
71A71338260497FF004095EE /* SwiftLint */,
52D6D9771BEFF229002C0205 /* Sources */,
52D6D9781BEFF229002C0205 /* Frameworks */,
52D6D9791BEFF229002C0205 /* Headers */,
@ -300,6 +436,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "Defaults-watchOS" */;
buildPhases = (
71A713532604A090004095EE /* SwiftLint */,
52D6D9DD1BEFFF6E002C0205 /* Sources */,
52D6D9DE1BEFFF6E002C0205 /* Frameworks */,
52D6D9DF1BEFFF6E002C0205 /* Headers */,
@ -318,6 +455,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "Defaults-tvOS" */;
buildPhases = (
71A713472604A05A004095EE /* SwiftLint */,
52D6D9EB1BEFFFBE002C0205 /* Sources */,
52D6D9EC1BEFFFBE002C0205 /* Frameworks */,
52D6D9ED1BEFFFBE002C0205 /* Headers */,
@ -365,6 +503,8 @@
DD7502811C68FCFC006590AF /* PBXTargetDependency */,
);
name = "Defaults-macOS Tests";
packageProductDependencies = (
);
productName = "Defaults-OS Tests";
productReference = DD75027A1C68FCFC006590AF /* Defaults-macOS Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
@ -436,6 +576,8 @@
Base,
);
mainGroup = 52D6D9721BEFF229002C0205;
packageReferences = (
);
productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -504,6 +646,60 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
71A71338260497FF004095EE /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "swiftlint\n";
};
71A713472604A05A004095EE /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "swiftlint\n";
};
71A713532604A090004095EE /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = SwiftLint;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "swiftlint\n";
};
E3FD0A4B25BDA35F0011D293 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -529,13 +725,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
71A7132D260497EE004095EE /* Migration+Protocol.swift in Sources */,
71A7132E260497EE004095EE /* Migration+Extensions.swift in Sources */,
71A7132F260497EE004095EE /* Migration+UserDefaults.swift in Sources */,
71A71330260497EE004095EE /* Migration+Defaults.swift in Sources */,
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */,
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
719F5E21258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
E3EB3E33216505920033B089 /* Utilities.swift in Sources */,
718B784025917D09004FF90D /* Defaults+Extensions.swift in Sources */,
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
718B783425917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -543,7 +746,22 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7152A04D25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */,
71573E9925A445CE00F18D4E /* DefaultsCollectionTests.swift in Sources */,
71362ACB25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */,
71F002F925959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */,
7108EAC125942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */,
71F0031A2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */,
71C56007259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */,
71C5602E259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */,
7108EAF425949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */,
71F003042595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */,
8933C7901EB5B82D000D00A4 /* DefaultsTests.swift in Sources */,
71B96F1E259986F100079F69 /* DefaultsCodableTests.swift in Sources */,
7150D6F425C2968700201966 /* DefaultsMigrationTests.swift in Sources */,
71F0030F2595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */,
7191AD872591977700AD472F /* DefaultsArrayTests.swift in Sources */,
71362AC025A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -551,13 +769,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
71A7135B2604A13B004095EE /* Migration+Protocol.swift in Sources */,
71A7135C2604A13B004095EE /* Migration+Extensions.swift in Sources */,
71A7135D2604A13B004095EE /* Migration+UserDefaults.swift in Sources */,
71A7135E2604A13B004095EE /* Migration+Defaults.swift in Sources */,
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */,
E3EB3E3A216507C40033B089 /* Utilities.swift in Sources */,
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
719F5E23258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
718B784225917D09004FF90D /* Defaults+Extensions.swift in Sources */,
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */,
718B783625917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -565,13 +790,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
71A713482604A084004095EE /* Migration+Protocol.swift in Sources */,
71A713492604A084004095EE /* Migration+Extensions.swift in Sources */,
71A7134A2604A084004095EE /* Migration+UserDefaults.swift in Sources */,
71A7134B2604A084004095EE /* Migration+Defaults.swift in Sources */,
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */,
E3EB3E3B216507C40033B089 /* Utilities.swift in Sources */,
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
719F5E22258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
718B784125917D09004FF90D /* Defaults+Extensions.swift in Sources */,
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */,
718B783525917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -581,11 +813,18 @@
files = (
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */,
7168638325E886DB00F55131 /* Migration+Defaults.swift in Sources */,
E3EB3E39216507C30033B089 /* Utilities.swift in Sources */,
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
719F5E20258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
E3EB3E36216507B50033B089 /* Observation.swift in Sources */,
71056FF425DE6DEF00524EDA /* Migration+Protocol.swift in Sources */,
71056FF525DE6DEF00524EDA /* Migration+Extensions.swift in Sources */,
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */,
718B783F25917D09004FF90D /* Defaults+Extensions.swift in Sources */,
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */,
71056FF625DE6DEF00524EDA /* Migration+UserDefaults.swift in Sources */,
718B783325917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -593,7 +832,22 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7152A04E25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */,
71573EA125A445D400F18D4E /* DefaultsCollectionTests.swift in Sources */,
71362ACC25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */,
71C55FF6259C13190053CCB3 /* DefaultsNSColorTests.swift in Sources */,
71F002FA25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */,
7108EAC225942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */,
71F0031B2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */,
71C5602F259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */,
7108EAF525949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */,
71F003052595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */,
8933C78F1EB5B82C000D00A4 /* DefaultsTests.swift in Sources */,
71B96F1F259986F100079F69 /* DefaultsCodableTests.swift in Sources */,
7150D6F525C2968700201966 /* DefaultsMigrationTests.swift in Sources */,
71F003102595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */,
7191AD882591977700AD472F /* DefaultsArrayTests.swift in Sources */,
71362AC125A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -601,7 +855,22 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7152A04F25A5BF6200CC9BA5 /* DefaultsSetAlgebraCustomElementTests.swift in Sources */,
71573EA925A445DB00F18D4E /* DefaultsCollectionTests.swift in Sources */,
71362ACD25A567AD00FAA91B /* DefaultsSetAlgebraTests.swift in Sources */,
71F002FB25959000001A1864 /* DefaultsNSSecureCodingTests.swift in Sources */,
7108EAC325942BF60013A623 /* DefaultsSwiftUITests.swift in Sources */,
71F0031C2595E15B001A1864 /* DefaultsCodableEnumTests.swift in Sources */,
71C56009259C25080053CCB3 /* DefaultsUIColorTests.swift in Sources */,
71C56030259C2A950053CCB3 /* DefaultsSetTests.swift in Sources */,
7108EAF625949DBF0013A623 /* DefaultsDictionaryTests.swift in Sources */,
71F003062595B460001A1864 /* DefaultsCustomBridgeTests.swift in Sources */,
8933C78E1EB5B82C000D00A4 /* DefaultsTests.swift in Sources */,
71B96F20259986F100079F69 /* DefaultsCodableTests.swift in Sources */,
7150D6F625C2968700201966 /* DefaultsMigrationTests.swift in Sources */,
71F003112595C7B8001A1864 /* DefaultsEnumTests.swift in Sources */,
7191AD892591977700AD472F /* DefaultsArrayTests.swift in Sources */,
71362AC225A5493300FAA91B /* DefaultsCollectionCustomElementTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -39,7 +40,6 @@
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -40,7 +40,6 @@
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -39,7 +39,6 @@
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"

View File

@ -0,0 +1,309 @@
import Foundation
#if os(macOS)
import AppKit
#else
import UIKit
#endif
extension Defaults.CodableBridge {
public func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
do {
// Some codable values like URL and enum are encoded as a top-level
// string which JSON can't handle, so we need to wrap it in an array
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
let data = try JSONEncoder().encode([value])
return String(String(data: data, encoding: .utf8)!.dropFirst().dropLast())
} catch {
print(error)
return nil
}
}
public func deserialize(_ object: Serializable?) -> Value? {
guard let jsonString = object else {
return nil
}
return [Value].init(jsonString: "[\(jsonString)]")?.first
}
}
/**
Any `Value` which protocol conforms to `Codable` and `Defaults.Serializable` will use `CodableBridge`
to do the serialization and deserialization.
*/
extension Defaults {
public struct TopLevelCodableBridge<Value: Codable>: CodableBridge {}
}
/**
`RawRepresentableCodableBridge` is indeed because if `enum SomeEnum: String, Codable, Defaults.Serializable`
the compiler will confuse between `RawRepresentableBridge` and `TopLevelCodableBridge`.
*/
extension Defaults {
public struct RawRepresentableCodableBridge<Value: RawRepresentable & Codable>: CodableBridge {}
}
extension Defaults {
public struct URLBridge: CodableBridge {
public typealias Value = URL
}
}
extension Defaults {
public struct RawRepresentableBridge<Value: RawRepresentable>: Defaults.Bridge {
public typealias Value = Value
public typealias Serializable = Value.RawValue
public func serialize(_ value: Value?) -> Serializable? {
value?.rawValue
}
public func deserialize(_ object: Serializable?) -> Value? {
guard let rawValue = object else {
return nil
}
return Value(rawValue: rawValue)
}
}
}
extension Defaults {
public struct NSSecureCodingBridge<Value: NSSecureCoding>: Defaults.Bridge {
public typealias Value = Value
public typealias Serializable = Data
public func serialize(_ value: Value?) -> Serializable? {
guard let object = value else {
return nil
}
// Version below macOS 10.13 and iOS 11.0 does not support `archivedData(withRootObject:requiringSecureCoding:)`.
// We need to set `requiresSecureCoding` by ourself.
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
return try? NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: true)
} else {
let keyedArchiver = NSKeyedArchiver()
keyedArchiver.requiresSecureCoding = true
keyedArchiver.encode(object, forKey: NSKeyedArchiveRootObjectKey)
return keyedArchiver.encodedData
}
}
public func deserialize(_ object: Serializable?) -> Value? {
guard let data = object else {
return nil
}
do {
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Value
} catch {
print(error)
return nil
}
}
}
}
extension Defaults {
public struct OptionalBridge<Wrapped: Defaults.Serializable>: Defaults.Bridge {
public typealias Value = Wrapped.Value
public typealias Serializable = Wrapped.Serializable
public func serialize(_ value: Value?) -> Serializable? {
Wrapped.bridge.serialize(value)
}
public func deserialize(_ object: Serializable?) -> Value? {
Wrapped.bridge.deserialize(object)
}
}
}
extension Defaults {
public struct ArrayBridge<Element: Defaults.Serializable>: Defaults.Bridge {
public typealias Value = [Element]
public typealias Serializable = [Element.Serializable]
public func serialize(_ value: Value?) -> Serializable? {
guard let array = value as? [Element.Value] else {
return nil
}
return array.map { Element.bridge.serialize($0) }.compact()
}
public func deserialize(_ object: Serializable?) -> Value? {
guard let array = object else {
return nil
}
return array.map { Element.bridge.deserialize($0) }.compact() as? Value
}
}
}
extension Defaults {
public struct DictionaryBridge<Key: LosslessStringConvertible & Hashable, Element: Defaults.Serializable>: Defaults.Bridge {
public typealias Value = [Key: Element.Value]
public typealias Serializable = [String: Element.Serializable]
public func serialize(_ value: Value?) -> Serializable? {
guard let dictionary = value else {
return nil
}
// `Key` which stored in `UserDefaults` have to be `String`
return dictionary.reduce(into: Serializable()) { memo, tuple in
memo[String(tuple.key)] = Element.bridge.serialize(tuple.value)
}
}
public func deserialize(_ object: Serializable?) -> Value? {
guard let dictionary = object else {
return nil
}
return dictionary.reduce(into: Value()) { memo, tuple in
// Use `LosslessStringConvertible` to create `Key` instance
guard let key = Key(tuple.key) else {
return
}
memo[key] = Element.bridge.deserialize(tuple.value)
}
}
}
}
/**
We need both `SetBridge` and `SetAlgebraBridge`.
Because `Set` conforms to `Sequence` but `SetAlgebra` not.
Set conforms to `Sequence`, so we can convert it into an array with `Array.init<S>(S)` and store it in the `UserDefaults`.
But `SetAlgebra` does not, so it is hard to convert it into an array.
Thats why we need `Defaults.SetAlgebraSerializable` protocol to convert it into an array.
*/
extension Defaults {
public struct SetBridge<Element: Defaults.Serializable & Hashable>: Defaults.Bridge {
public typealias Value = Set<Element>
public typealias Serializable = Any
public func serialize(_ value: Value?) -> Serializable? {
guard let set = value else {
return nil
}
if Element.isNativelySupportedType {
return Array(set)
}
return set.map { Element.bridge.serialize($0 as? Element.Value) }.compact()
}
public func deserialize(_ object: Serializable?) -> Value? {
if Element.isNativelySupportedType {
guard let array = object as? [Element] else {
return nil
}
return Set(array)
}
guard
let array = object as? [Element.Serializable],
let elements = array.map({ Element.bridge.deserialize($0) }).compact() as? [Element]
else {
return nil
}
return Set(elements)
}
}
}
extension Defaults {
public struct SetAlgebraBridge<Value: Defaults.SetAlgebraSerializable>: Defaults.Bridge where Value.Element: Defaults.Serializable {
public typealias Value = Value
public typealias Element = Value.Element
public typealias Serializable = Any
public func serialize(_ value: Value?) -> Serializable? {
guard let setAlgebra = value else {
return nil
}
if Element.isNativelySupportedType {
return setAlgebra.toArray()
}
return setAlgebra.toArray().map { Element.bridge.serialize($0 as? Element.Value) }.compact()
}
public func deserialize(_ object: Serializable?) -> Value? {
if Element.isNativelySupportedType {
guard let array = object as? [Element] else {
return nil
}
return Value(array)
}
guard
let array = object as? [Element.Serializable],
let elements = array.map({ Element.bridge.deserialize($0) }).compact() as? [Element]
else {
return nil
}
return Value(elements)
}
}
}
extension Defaults {
public struct CollectionBridge<Value: Defaults.CollectionSerializable>: Defaults.Bridge where Value.Element: Defaults.Serializable {
public typealias Value = Value
public typealias Element = Value.Element
public typealias Serializable = Any
public func serialize(_ value: Value?) -> Serializable? {
guard let collection = value else {
return nil
}
if Element.isNativelySupportedType {
return Array(collection)
}
return collection.map { Element.bridge.serialize($0 as? Element.Value) }.compact()
}
public func deserialize(_ object: Serializable?) -> Value? {
if Element.isNativelySupportedType {
guard let array = object as? [Element] else {
return nil
}
return Value(array)
}
guard
let array = object as? [Element.Serializable],
let elements = array.map({ Element.bridge.deserialize($0) }).compact() as? [Element]
else {
return nil
}
return Value(elements)
}
}
}

View File

@ -0,0 +1,135 @@
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
extension Defaults.Serializable {
public static var isNativelySupportedType: Bool { false }
}
extension Data: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Date: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Bool: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Int: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension UInt: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Double: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Float: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension String: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension CGFloat: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Int8: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension UInt8: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Int16: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension UInt16: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Int32: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension UInt32: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension Int64: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension UInt64: Defaults.Serializable {
public static let isNativelySupportedType = true
}
extension URL: Defaults.Serializable {
public static let bridge = Defaults.URLBridge()
}
extension Defaults.Serializable where Self: Codable {
public static var bridge: Defaults.TopLevelCodableBridge<Self> { Defaults.TopLevelCodableBridge() }
}
extension Defaults.Serializable where Self: RawRepresentable {
public static var bridge: Defaults.RawRepresentableBridge<Self> { Defaults.RawRepresentableBridge() }
}
extension Defaults.Serializable where Self: RawRepresentable & Codable {
public static var bridge: Defaults.RawRepresentableCodableBridge<Self> { Defaults.RawRepresentableCodableBridge() }
}
extension Defaults.Serializable where Self: NSSecureCoding {
public static var bridge: Defaults.NSSecureCodingBridge<Self> { Defaults.NSSecureCodingBridge() }
}
extension Optional: Defaults.Serializable where Wrapped: Defaults.Serializable {
public static var isNativelySupportedType: Bool { Wrapped.isNativelySupportedType }
public static var bridge: Defaults.OptionalBridge<Wrapped> { Defaults.OptionalBridge() }
}
extension Defaults.CollectionSerializable where Element: Defaults.Serializable {
public static var bridge: Defaults.CollectionBridge<Self> { Defaults.CollectionBridge() }
}
extension Defaults.SetAlgebraSerializable where Element: Defaults.Serializable & Hashable {
public static var bridge: Defaults.SetAlgebraBridge<Self> { Defaults.SetAlgebraBridge() }
}
extension Set: Defaults.Serializable where Element: Defaults.Serializable {
public static var bridge: Defaults.SetBridge<Element> { Defaults.SetBridge() }
}
extension Array: Defaults.Serializable where Element: Defaults.Serializable {
public static var isNativelySupportedType: Bool { Element.isNativelySupportedType }
public static var bridge: Defaults.ArrayBridge<Element> { Defaults.ArrayBridge() }
}
extension Dictionary: Defaults.Serializable where Key: LosslessStringConvertible & Hashable, Value: Defaults.Serializable {
public static var isNativelySupportedType: Bool { Value.isNativelySupportedType }
public static var bridge: Defaults.DictionaryBridge<Key, Value> { Defaults.DictionaryBridge() }
}
#if os(macOS)
/// `NSColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`
extension NSColor: Defaults.Serializable {}
#else
/// `UIColor` conforms to `NSSecureCoding`, so it goes to `NSSecureCodingBridge`
extension UIColor: Defaults.Serializable {}
#endif

View File

@ -0,0 +1,93 @@
import Foundation
/**
All type that able to work with `Defaults` should conform this protocol.
It should have a static variable bridge which protocol should conform to `Defaults.Bridge`.
```
struct User {
username: String
password: String
}
extension User: Defaults.Serializable {
static let bridge = UserBridge()
}
```
*/
public protocol DefaultsSerializable {
typealias Value = Bridge.Value
typealias Serializable = Bridge.Serializable
associatedtype Bridge: DefaultsBridge
/// Static bridge for the `Value` which cannot store natively
static var bridge: Bridge { get }
/// A flag to determine whether `Value` can be store natively or not
static var isNativelySupportedType: Bool { get }
}
/**
A Bridge can do the serialization and de-serialization.
Have two associate types `Value` and `Serializable`.
- `Value`: the type user want to use it.
- `Serializable`: the type stored in `UserDefaults`.
- `serialize`: will be executed before storing to the `UserDefaults` .
- `deserialize`: will be executed after retrieving its value from the `UserDefaults`.
```
struct User {
username: String
password: String
}
struct UserBridge: Defaults.Bridge {
typealias Value = User
typealias Serializable = [String: String]
func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return ["username": value.username, "password": value.password]
}
func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let username = object["username"],
let password = object["password"]
else {
return nil
}
return User(username: username, password: password)
}
}
```
*/
public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize(_ value: Value?) -> Serializable?
func deserialize(_ object: Serializable?) -> Value?
}
public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable {
/// `Collection` does not have initializer, but we need initializer to convert an array into the `Value`
init(_ elements: [Element])
}
public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable {
/// Since `SetAlgebra` protocol does not conform to `Sequence`, we cannot convert a `SetAlgebra` to an `Array` directly.
func toArray() -> [Element]
}
/// Convenience protocol for `Codable`
public protocol DefaultsCodableBridge: DefaultsBridge where Serializable == String, Value: Codable {}

View File

@ -16,16 +16,15 @@ extension DefaultsBaseKey {
public enum Defaults {
public typealias BaseKey = DefaultsBaseKey
public typealias AnyKey = Keys
public typealias Serializable = DefaultsSerializable
public typealias CollectionSerializable = DefaultsCollectionSerializable
public typealias SetAlgebraSerializable = DefaultsSetAlgebraSerializable
public typealias Bridge = DefaultsBridge
typealias CodableBridge = DefaultsCodableBridge
public class Keys: BaseKey {
public typealias Key = Defaults.Key
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public typealias NSSecureCodingKey = Defaults.NSSecureCodingKey
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public typealias NSSecureCodingOptionalKey = Defaults.NSSecureCodingOptionalKey
public let name: String
public let suite: UserDefaults
@ -35,7 +34,7 @@ public enum Defaults {
}
}
public final class Key<Value: Codable>: AnyKey {
public final class Key<Value: Serializable>: AnyKey {
public let defaultValue: Value
/// Create a defaults key.
@ -49,67 +48,16 @@ public enum Defaults {
return
}
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
if UserDefaults.isNativelySupportedType(Value.self) {
suite.register(defaults: [key: defaultValue])
} else if let value = suite._encode(defaultValue) {
suite.register(defaults: [key: value])
}
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public final class NSSecureCodingKey<Value: NSSecureCoding>: AnyKey {
public let defaultValue: Value
/// Create a defaults key.
/// The `default` parameter can be left out if the `Value` type is an optional.
public init(_ key: String, default defaultValue: Value, suite: UserDefaults = .standard) {
self.defaultValue = defaultValue
super.init(name: key, suite: suite)
if (defaultValue as? _DefaultsOptionalType)?.isNil == true {
guard let serialized = Value.toSerializable(defaultValue) else {
return
}
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
if UserDefaults.isNativelySupportedType(Value.self) {
suite.register(defaults: [key: defaultValue])
} else if let value = try? NSKeyedArchiver.archivedData(withRootObject: defaultValue, requiringSecureCoding: true) {
suite.register(defaults: [key: value])
}
suite.register(defaults: [self.name: serialized])
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public final class NSSecureCodingOptionalKey<Value: NSSecureCoding>: AnyKey {
/// Create an optional defaults key.
public init(_ key: String, suite: UserDefaults = .standard) {
super.init(name: key, suite: suite)
}
}
/// Access a defaults value using a `Defaults.Key`.
public static subscript<Value>(key: Key<Value>) -> Value {
get { key.suite[key] }
set {
key.suite[key] = newValue
}
}
/// Access a defaults value using a `Defaults.NSSecureCodingKey`.
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public static subscript<Value>(key: NSSecureCodingKey<Value>) -> Value {
get { key.suite[key] }
set {
key.suite[key] = newValue
}
}
/// Access a defaults value using a `Defaults.NSSecureCodingOptionalKey`.
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public static subscript<Value>(key: NSSecureCodingOptionalKey<Value>) -> Value? {
public static subscript<Value: Serializable>(key: Key<Value>) -> Value {
get { key.suite[key] }
set {
key.suite[key] = newValue
@ -129,14 +77,7 @@ extension Defaults {
}
extension Defaults.Key {
public convenience init<T>(_ key: String, suite: UserDefaults = .standard) where Value == T? {
self.init(key, default: nil, suite: suite)
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
extension Defaults.NSSecureCodingKey where Value: _DefaultsOptionalType {
public convenience init(_ key: String, suite: UserDefaults = .standard) {
public convenience init<T: Defaults.Serializable>(_ key: String, suite: UserDefaults = .standard) where Value == T? {
self.init(key, default: nil, suite: suite)
}
}

View File

@ -0,0 +1,46 @@
import Foundation
extension Defaults {
public enum Version: Int {
case v5 = 5
}
/**
Migration the given key's value from json string to `Value`.
```
extension Defaults.Keys {
static let array = Key<Set<String>?>("array")
}
Defaults.migrate(.array, to: .v5)
```
*/
public static func migrate<Value: Defaults.Serializable & Codable>(_ keys: Key<Value>..., to version: Version) {
migrate(keys, to: version)
}
public static func migrate<Value: Defaults.NativeType>(_ keys: Key<Value>..., to version: Version) {
migrate(keys, to: version)
}
public static func migrate<Value: Defaults.Serializable & Codable>(_ keys: [Key<Value>], to version: Version) {
switch version {
case .v5:
for key in keys {
let suite = key.suite
suite.migrateCodableToNative(forKey: key.name, of: Value.self)
}
}
}
public static func migrate<Value: Defaults.NativeType>(_ keys: [Key<Value>], to version: Version) {
switch version {
case .v5:
for key in keys {
let suite = key.suite
suite.migrateCodableToNative(forKey: key.name, of: Value.self)
}
}
}
}

View File

@ -0,0 +1,251 @@
import Foundation
import CoreGraphics
extension Defaults {
public typealias NativeType = DefaultsNativeType
public typealias CodableType = DefaultsCodableType
}
extension Data: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Data: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Date: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Date: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Bool: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Bool: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Int: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Int: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension UInt: Defaults.NativeType {
public typealias CodableForm = Self
}
extension UInt: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Double: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Double: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Float: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Float: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension String: Defaults.NativeType {
public typealias CodableForm = Self
}
extension String: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension CGFloat: Defaults.NativeType {
public typealias CodableForm = Self
}
extension CGFloat: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Int8: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Int8: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension UInt8: Defaults.NativeType {
public typealias CodableForm = Self
}
extension UInt8: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Int16: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Int16: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension UInt16: Defaults.NativeType {
public typealias CodableForm = Self
}
extension UInt16: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Int32: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Int32: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension UInt32: Defaults.NativeType {
public typealias CodableForm = Self
}
extension UInt32: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Int64: Defaults.NativeType {
public typealias CodableForm = Self
}
extension Int64: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension UInt64: Defaults.NativeType {
public typealias CodableForm = Self
}
extension UInt64: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension URL: Defaults.NativeType {
public typealias CodableForm = Self
}
extension URL: Defaults.CodableType {
public typealias NativeForm = Self
public func toNative() -> Self {
self
}
}
extension Optional: Defaults.NativeType where Wrapped: Defaults.NativeType {
public typealias CodableForm = Wrapped.CodableForm
}
extension Defaults.CollectionSerializable where Self: Defaults.NativeType, Element: Defaults.NativeType {
public typealias CodableForm = [Element.CodableForm]
}
extension Defaults.SetAlgebraSerializable where Self: Defaults.NativeType, Element: Defaults.NativeType {
public typealias CodableForm = [Element.CodableForm]
}
extension Defaults.CodableType where Self: RawRepresentable, NativeForm: RawRepresentable, Self.RawValue == NativeForm.RawValue {
public func toNative() -> NativeForm {
NativeForm(rawValue: self.rawValue)!
}
}
extension Set: Defaults.NativeType where Element: Defaults.NativeType {
public typealias CodableForm = [Element.CodableForm]
}
extension Array: Defaults.NativeType where Element: Defaults.NativeType {
public typealias CodableForm = [Element.CodableForm]
}
extension Array: Defaults.CodableType where Element: Defaults.CodableType {
public typealias NativeForm = [Element.NativeForm]
public func toNative() -> NativeForm {
map { $0.toNative() }
}
}
extension Dictionary: Defaults.NativeType where Key: LosslessStringConvertible & Hashable, Value: Defaults.NativeType {
public typealias CodableForm = [String: Value.CodableForm]
}
extension Dictionary: Defaults.CodableType where Key == String, Value: Defaults.CodableType {
public typealias NativeForm = [String: Value.NativeForm]
public func toNative() -> NativeForm {
reduce(into: NativeForm()) { memo, tuple in
memo[tuple.key] = tuple.value.toNative()
}
}
}

View File

@ -0,0 +1,62 @@
import Foundation
/**
Only for migration.
Represents the type after migration and its protocol should conform to `Defaults.Serializable`.
It should have an associated type name `CodableForm` which protocol conform to `Codable`.
So we can convert the json string into `NativeType` like this.
```
guard
let jsonString = string,
let jsonData = jsonString.data(using: .utf8),
let codable = try? JSONDecoder().decode(NativeType.CodableForm.self, from: jsonData)
else {
return nil
}
return codable.toNative()
```
*/
public protocol DefaultsNativeType: Defaults.Serializable {
associatedtype CodableForm: Defaults.CodableType
}
/**
Only for migration.
Represents the type before migration an its protocol should conform to `Codable`.
The main purposed of `CodableType` is trying to infer the `Codable` type to do `JSONDecoder().decode`
It should have an associated type name `NativeForm` which is the type we want it to store in `UserDefaults`.
And it also have a `toNative()` function to convert itself into `NativeForm`.
```
struct User {
username: String
password: String
}
struct CodableUser: Codable {
username: String
password: String
}
extension User: Defaults.NativeType {
typealias CodableForm = CodableUser
}
extension CodableUser: Defaults.CodableType {
typealias NativeForm = User
func toNative() -> NativeForm {
User(username: self.username, password: self.password)
}
}
```
*/
public protocol DefaultsCodableType: Codable {
associatedtype NativeForm: Defaults.NativeType
func toNative() -> NativeForm
}

View File

@ -0,0 +1,41 @@
import Foundation
extension UserDefaults {
func migrateCodableToNative<Value: Defaults.Serializable & Codable>(forKey key: String, of type: Value.Type) {
guard
let jsonString = string(forKey: key),
let jsonData = jsonString.data(using: .utf8),
let codable = try? JSONDecoder().decode(Value.self, from: jsonData)
else {
return
}
_set(key, to: codable)
}
/**
Get json string in `UserDefaults` and decode it into the `NativeForm`.
How it works?
For example:
Step1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`.
`JSONDecoder().decode([String].CodableForm.self, from: jsonData)`
Step2. `Array`conform to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`.
`JSONDecoder().decode([String.CodableForm].self, from: jsonData)`
Step3. `String`'s `CodableForm` is `self`, because `String` is `Codable`.
`JSONDecoder().decode([String].self, from: jsonData)`
*/
func migrateCodableToNative<Value: Defaults.NativeType>(forKey key: String, of type: Value.Type) {
guard
let jsonString = string(forKey: key),
let jsonData = jsonString.data(using: .utf8),
let codable = try? JSONDecoder().decode(Value.CodableForm.self, from: jsonData)
else {
return
}
_set(key, to: codable.toNative())
}
}

View File

@ -88,7 +88,7 @@ extension Defaults {
```
*/
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
public static func publisher<Value>(
public static func publisher<Value: Serializable>(
_ key: Key<Value>,
options: ObservationOptions = [.initial]
) -> AnyPublisher<KeyChange<Value>, Never> {
@ -98,34 +98,6 @@ extension Defaults {
return AnyPublisher(publisher)
}
/**
Returns a type-erased `Publisher` that publishes changes related to the given key.
*/
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
public static func publisher<Value>(
_ key: NSSecureCodingKey<Value>,
options: ObservationOptions = [.initial]
) -> AnyPublisher<NSSecureCodingKeyChange<Value>, Never> {
let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options)
.map { NSSecureCodingKeyChange<Value>(change: $0, defaultValue: key.defaultValue) }
return AnyPublisher(publisher)
}
/**
Returns a type-erased `Publisher` that publishes changes related to the given optional key.
*/
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
public static func publisher<Value>(
_ key: NSSecureCodingOptionalKey<Value>,
options: ObservationOptions = [.initial]
) -> AnyPublisher<NSSecureCodingOptionalKeyChange<Value>, Never> {
let publisher = DefaultsPublisher(suite: key.suite, key: key.name, options: options)
.map { NSSecureCodingOptionalKeyChange<Value>(change: $0) }
return AnyPublisher(publisher)
}
/**
Publisher for multiple `Key<T>` observation, but without specific information about changes.
*/

View File

@ -37,7 +37,7 @@ extension Defaults {
public typealias ObservationOptions = Set<ObservationOption>
private static func deserialize<Value: Decodable>(_ value: Any?, to type: Value.Type) -> Value? {
private static func deserialize<Value: Serializable>(_ value: Any?, to type: Value.Type) -> Value? {
guard
let value = value,
!(value is NSNull)
@ -45,34 +45,7 @@ extension Defaults {
return nil
}
// This handles the case where the value was a plist value using `isNativelySupportedType`
if let value = value as? Value {
return value
}
// Using the array trick as done below in `UserDefaults#_set()`
return [Value].init(jsonString: "\([value])")?.first
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
private static func deserialize<Value: NSSecureCoding>(_ value: Any?, to type: Value.Type) -> Value? {
guard
let value = value,
!(value is NSNull)
else {
return nil
}
// This handles the case where the value was a plist value using `isNativelySupportedType`
if let value = value as? Value {
return value
}
guard let dataValue = value as? Data else {
return nil
}
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(dataValue) as? Value
return Value.toValue(value)
}
struct BaseChange {
@ -91,7 +64,7 @@ extension Defaults {
}
}
public struct KeyChange<Value: Codable> {
public struct KeyChange<Value: Serializable> {
public let kind: NSKeyValueChange
public let indexes: IndexSet?
public let isPrior: Bool
@ -107,40 +80,6 @@ extension Defaults {
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public struct NSSecureCodingKeyChange<Value: NSSecureCoding> {
public let kind: NSKeyValueChange
public let indexes: IndexSet?
public let isPrior: Bool
public let newValue: Value
public let oldValue: Value
init(change: BaseChange, defaultValue: Value) {
self.kind = change.kind
self.indexes = change.indexes
self.isPrior = change.isPrior
self.oldValue = deserialize(change.oldValue, to: Value.self) ?? defaultValue
self.newValue = deserialize(change.newValue, to: Value.self) ?? defaultValue
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public struct NSSecureCodingOptionalKeyChange<Value: NSSecureCoding> {
public let kind: NSKeyValueChange
public let indexes: IndexSet?
public let isPrior: Bool
public let newValue: Value?
public let oldValue: Value?
init(change: BaseChange) {
self.kind = change.kind
self.indexes = change.indexes
self.isPrior = change.isPrior
self.oldValue = deserialize(change.oldValue, to: Value.self)
self.newValue = deserialize(change.newValue, to: Value.self)
}
}
private static var preventPropagationThreadDictionaryKey: String {
"\(type(of: Observation.self))_threadUpdatingValuesFlag"
}
@ -242,7 +181,6 @@ extension Defaults {
guard !updatingValuesFlag else {
return
}
callback(BaseChange(change: change))
}
}
@ -352,7 +290,7 @@ extension Defaults {
}
```
*/
public static func observe<Value>(
public static func observe<Value: Serializable>(
_ key: Key<Value>,
options: ObservationOptions = [.initial],
handler: @escaping (KeyChange<Value>) -> Void
@ -366,41 +304,6 @@ extension Defaults {
return observation
}
/**
Observe a defaults key.
*/
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public static func observe<Value>(
_ key: NSSecureCodingKey<Value>,
options: ObservationOptions = [.initial],
handler: @escaping (NSSecureCodingKeyChange<Value>) -> Void
) -> Observation {
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
handler(
NSSecureCodingKeyChange(change: change, defaultValue: key.defaultValue)
)
}
observation.start(options: options)
return observation
}
/**
Observe an optional defaults key.
*/
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public static func observe<Value>(
_ key: NSSecureCodingOptionalKey<Value>,
options: ObservationOptions = [.initial],
handler: @escaping (NSSecureCodingOptionalKeyChange<Value>) -> Void
) -> Observation {
let observation = UserDefaultsKeyObservation(object: key.suite, key: key.name) { change in
handler(
NSSecureCodingOptionalKeyChange(change: change)
)
}
observation.start(options: options)
return observation
}
/**
Observe multiple keys of any type, but without any information about the changes.
@ -447,10 +350,4 @@ extension Defaults.ObservationOptions {
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
extension Defaults.NSSecureCodingKeyChange: Equatable where Value: Equatable { }
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
extension Defaults.NSSecureCodingOptionalKeyChange: Equatable where Value: Equatable { }
extension Defaults.KeyChange: Equatable where Value: Equatable { }

View File

@ -4,7 +4,7 @@ import Combine
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Defaults {
final class Observable<Value: Codable>: ObservableObject {
final class Observable<Value: Serializable>: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
private var observation: DefaultsObservation?
private let key: Defaults.Key<Value>
@ -40,7 +40,7 @@ extension Defaults {
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper
public struct Default<Value: Codable>: DynamicProperty {
public struct Default<Value: Defaults.Serializable>: DynamicProperty {
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
private let key: Defaults.Key<Value>

View File

@ -1,136 +1,29 @@
import Foundation
extension UserDefaults {
private func _get<Value: Codable>(_ key: String) -> Value? {
if UserDefaults.isNativelySupportedType(Value.self) {
return object(forKey: key) as? Value
}
guard
let text = string(forKey: key),
let data = "[\(text)]".data(using: .utf8)
else {
func _get<Value: Defaults.Serializable>(_ key: String) -> Value? {
guard let anyObject = object(forKey: key) else {
return nil
}
do {
return (try JSONDecoder().decode([Value].self, from: data)).first
} catch {
print(error)
}
return nil
return Value.toValue(anyObject)
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
private func _get<Value: NSSecureCoding>(_ key: String) -> Value? {
if UserDefaults.isNativelySupportedType(Value.self) {
return object(forKey: key) as? Value
}
guard
let data = data(forKey: key)
else {
return nil
}
do {
return try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Value
} catch {
print(error)
}
return nil
}
func _encode<Value: Codable>(_ value: Value) -> String? {
do {
// Some codable values like URL and enum are encoded as a top-level
// string which JSON can't handle, so we need to wrap it in an array
// We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750
let data = try JSONEncoder().encode([value])
return String(String(data: data, encoding: .utf8)!.dropFirst().dropLast())
} catch {
print(error)
return nil
}
}
private func _set<Value: Codable>(_ key: String, to value: Value) {
func _set<Value: Defaults.Serializable>(_ key: String, to value: Value) {
if (value as? _DefaultsOptionalType)?.isNil == true {
removeObject(forKey: key)
return
}
if UserDefaults.isNativelySupportedType(Value.self) {
set(value, forKey: key)
return
}
set(_encode(value), forKey: key)
set(Value.toSerializable(value), forKey: key)
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
private func _set<Value: NSSecureCoding>(_ key: String, to value: Value) {
// TODO: Handle nil here too.
if UserDefaults.isNativelySupportedType(Value.self) {
set(value, forKey: key)
return
}
set(try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true), forKey: key)
}
public subscript<Value>(key: Defaults.Key<Value>) -> Value {
public subscript<Value: Defaults.Serializable>(key: Defaults.Key<Value>) -> Value {
get { _get(key.name) ?? key.defaultValue }
set {
_set(key.name, to: newValue)
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public subscript<Value>(key: Defaults.NSSecureCodingKey<Value>) -> Value {
get { _get(key.name) ?? key.defaultValue }
set {
_set(key.name, to: newValue)
}
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
public subscript<Value>(key: Defaults.NSSecureCodingOptionalKey<Value>) -> Value? {
get { _get(key.name) }
set {
guard let value = newValue else {
set(nil, forKey: key.name)
return
}
_set(key.name, to: value)
}
}
static func isNativelySupportedType<T>(_ type: T.Type) -> Bool {
switch type {
case
is Bool.Type,
is Bool?.Type, // swiftlint:disable:this discouraged_optional_boolean
is String.Type,
is String?.Type,
is Int.Type,
is Int?.Type,
is Double.Type,
is Double?.Type,
is Float.Type,
is Float?.Type,
is Date.Type,
is Date?.Type,
is Data.Type,
is Data?.Type:
return true
default:
return false
}
}
}
extension UserDefaults {

View File

@ -146,3 +146,55 @@ extension DispatchQueue {
}
}
}
extension Sequence {
/// Returns an array containing the non-nil elements.
func compact<T>() -> [T] where Element == T? {
// TODO: Make this `compactMap(\.self)` when https://bugs.swift.org/browse/SR-12897 is fixed.
compactMap { $0 }
}
}
extension Defaults.Serializable {
/**
Cast `Serializable` value to `Self`.
Convert the native support type from `UserDefaults` into `Self`.
```
guard let anyObject = object(forKey: key) else {
return nil
}
return Value.toValue(anyObject)
```
*/
static func toValue(_ anyObject: Any) -> Self? {
// Return directly if `anyObject` can cast to Value, means `Value` is Native supported type.
if Self.isNativelySupportedType, let anyObject = anyObject as? Self {
return anyObject
} else if let value = Self.bridge.deserialize(anyObject as? Serializable) {
return value as? Self
}
return nil
}
/**
Cast `Self` to `Serializable`.
Convert `Self` into `UserDefaults` native support type.
```
set(Value.toSerialize(value), forKey: key)
```
*/
static func toSerializable(_ value: Self) -> Any? {
// Return directly if `Self` is native supported type, since it does not need serialization.
if Self.isNativelySupportedType {
return value
} else if let serialized = Self.bridge.serialize(value as? Self.Value) {
return serialized
}
return nil
}
}

View File

@ -0,0 +1,178 @@
import Foundation
import Defaults
import XCTest
private let fixtureArray = ["Hank", "Chen"]
extension Defaults.Keys {
fileprivate static let array = Key<[String]>("array", default: fixtureArray)
}
final class DefaultsArrayTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<[String]>("independentArrayStringKey", default: fixtureArray)
XCTAssertEqual(Defaults[key][0], fixtureArray[0])
let newValue = "John"
Defaults[key][0] = newValue
XCTAssertEqual(Defaults[key][0], newValue)
}
func testOptionalKey() {
let key = Defaults.Key<[String]?>("independentArrayOptionalStringKey")
XCTAssertNil(Defaults[key])
Defaults[key] = fixtureArray
XCTAssertEqual(Defaults[key]?[0], fixtureArray[0])
Defaults[key] = nil
XCTAssertNil(Defaults[key])
let newValue = ["John", "Chen"]
Defaults[key] = newValue
XCTAssertEqual(Defaults[key]?[0], newValue[0])
}
func testNestedKey() {
let defaultValue = ["Hank", "Chen"]
let key = Defaults.Key<[[String]]>("independentArrayNestedKey", default: [defaultValue])
XCTAssertEqual(Defaults[key][0][0], "Hank")
let newValue = ["Sindre", "Sorhus"]
Defaults[key][0] = newValue
Defaults[key].append(defaultValue)
XCTAssertEqual(Defaults[key][0][0], newValue[0])
XCTAssertEqual(Defaults[key][0][1], newValue[1])
XCTAssertEqual(Defaults[key][1][0], defaultValue[0])
XCTAssertEqual(Defaults[key][1][1], defaultValue[1])
}
func testDictionaryKey() {
let defaultValue = ["0": "HankChen"]
let key = Defaults.Key<[[String: String]]>("independentArrayDictionaryKey", default: [defaultValue])
XCTAssertEqual(Defaults[key][0]["0"], defaultValue["0"])
let newValue = ["0": "SindreSorhus"]
Defaults[key][0] = newValue
Defaults[key].append(defaultValue)
XCTAssertEqual(Defaults[key][0]["0"], newValue["0"])
XCTAssertEqual(Defaults[key][1]["0"], defaultValue["0"])
}
func testNestedDictionaryKey() {
let defaultValue = ["0": [["0": 0]]]
let key = Defaults.Key<[[String: [[String: Int]]]]>("independentArrayNestedDictionaryKey", default: [defaultValue])
XCTAssertEqual(Defaults[key][0]["0"]![0]["0"], 0)
let newValue = 1
Defaults[key][0]["0"]![0]["0"] = newValue
Defaults[key].append(defaultValue)
XCTAssertEqual(Defaults[key][1]["0"]![0]["0"], 0)
XCTAssertEqual(Defaults[key][0]["0"]![0]["0"], newValue)
}
func testType() {
XCTAssertEqual(Defaults[.array][0], fixtureArray[0])
let newName = "Hank121314"
Defaults[.array][0] = newName
XCTAssertEqual(Defaults[.array][0], newName)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<[String]>("observeArrayKeyCombine", default: fixtureArray)
let newName = "Chen"
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureArray[0], newName), (newName, fixtureArray[0])].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key][0] = newName
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<[String]?>("observeArrayOptionalKeyCombine")
let newName = ["Chen"]
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
// swiftlint:disable discouraged_optional_collection
let expectedValues: [([String]?, [String]?)] = [(nil, fixtureArray), (fixtureArray, newName), (newName, nil)]
let cancellable = publisher.sink { actualValues in
for (expected, actual) in zip(expectedValues, actualValues) {
XCTAssertEqual(expected.0, actual.0)
XCTAssertEqual(expected.1, actual.1)
}
expect.fulfill()
}
Defaults[key] = fixtureArray
Defaults[key] = newName
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<[String]>("observeArrayKey", default: fixtureArray)
let newName = "John"
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, fixtureArray)
XCTAssertEqual(change.newValue, [fixtureArray[0], newName])
observation.invalidate()
expect.fulfill()
}
Defaults[key][1] = newName
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<[String]?>("observeArrayOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue!, fixtureArray)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureArray
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,305 @@
import Foundation
import Defaults
import XCTest
private enum FixtureCodableEnum: String, Defaults.Serializable & Codable & Hashable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension Defaults.Keys {
fileprivate static let codableEnum = Key<FixtureCodableEnum>("codable_enum", default: .oneHour)
fileprivate static let codableEnumArray = Key<[FixtureCodableEnum]>("codable_enum", default: [.oneHour])
fileprivate static let codableEnumDictionary = Key<[String: FixtureCodableEnum]>("codable_enum", default: ["0": .oneHour])
}
final class DefaultsCodableEnumTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<FixtureCodableEnum>("independentCodableEnumKey", default: .tenMinutes)
XCTAssertEqual(Defaults[key], .tenMinutes)
Defaults[key] = .halfHour
XCTAssertEqual(Defaults[key], .halfHour)
}
func testOptionalKey() {
let key = Defaults.Key<FixtureCodableEnum?>("independentCodableEnumOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = .tenMinutes
XCTAssertEqual(Defaults[key], .tenMinutes)
}
func testArrayKey() {
let key = Defaults.Key<[FixtureCodableEnum]>("independentCodableEnumArrayKey", default: [.tenMinutes])
XCTAssertEqual(Defaults[key][0], .tenMinutes)
Defaults[key][0] = .halfHour
XCTAssertEqual(Defaults[key][0], .halfHour)
}
func testArrayOptionalKey() {
let key = Defaults.Key<[FixtureCodableEnum]?>("independentCodableEnumArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [.halfHour]
}
func testNestedArrayKey() {
let key = Defaults.Key<[[FixtureCodableEnum]]>("independentCodableEnumNestedArrayKey", default: [[.tenMinutes]])
XCTAssertEqual(Defaults[key][0][0], .tenMinutes)
Defaults[key].append([.halfHour])
Defaults[key][0].append(.oneHour)
XCTAssertEqual(Defaults[key][1][0], .halfHour)
XCTAssertEqual(Defaults[key][0][1], .oneHour)
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: FixtureCodableEnum]]>("independentCodableEnumArrayDictionaryKey", default: [["0": .tenMinutes]])
XCTAssertEqual(Defaults[key][0]["0"], .tenMinutes)
Defaults[key][0]["1"] = .halfHour
Defaults[key].append(["0": .oneHour])
XCTAssertEqual(Defaults[key][0]["1"], .halfHour)
XCTAssertEqual(Defaults[key][1]["0"], .oneHour)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: FixtureCodableEnum]>("independentCodableEnumDictionaryKey", default: ["0": .tenMinutes])
XCTAssertEqual(Defaults[key]["0"], .tenMinutes)
Defaults[key]["1"] = .halfHour
Defaults[key]["0"] = .oneHour
XCTAssertEqual(Defaults[key]["0"], .oneHour)
XCTAssertEqual(Defaults[key]["1"], .halfHour)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: FixtureCodableEnum]?>("independentCodableEnumDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": .tenMinutes]
Defaults[key]?["1"] = .halfHour
XCTAssertEqual(Defaults[key]?["0"], .tenMinutes)
XCTAssertEqual(Defaults[key]?["1"], .halfHour)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [FixtureCodableEnum]]>("independentCodableEnumDictionaryArrayKey", default: ["0": [.tenMinutes]])
XCTAssertEqual(Defaults[key]["0"]?[0], .tenMinutes)
Defaults[key]["0"]?.append(.halfHour)
Defaults[key]["1"] = [.oneHour]
XCTAssertEqual(Defaults[key]["0"]?[0], .tenMinutes)
XCTAssertEqual(Defaults[key]["0"]?[1], .halfHour)
XCTAssertEqual(Defaults[key]["1"]?[0], .oneHour)
}
func testType() {
XCTAssertEqual(Defaults[.codableEnum], .oneHour)
Defaults[.codableEnum] = .tenMinutes
XCTAssertEqual(Defaults[.codableEnum], .tenMinutes)
}
func testArrayType() {
XCTAssertEqual(Defaults[.codableEnumArray][0], .oneHour)
Defaults[.codableEnumArray].append(.halfHour)
XCTAssertEqual(Defaults[.codableEnumArray][0], .oneHour)
XCTAssertEqual(Defaults[.codableEnumArray][1], .halfHour)
}
func testDictionaryType() {
XCTAssertEqual(Defaults[.codableEnumDictionary]["0"], .oneHour)
Defaults[.codableEnumDictionary]["1"] = .halfHour
XCTAssertEqual(Defaults[.codableEnumDictionary]["0"], .oneHour)
XCTAssertEqual(Defaults[.codableEnumDictionary]["1"], .halfHour)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<FixtureCodableEnum>("observeCodableEnumKeyCombine", default: .tenMinutes)
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(FixtureCodableEnum, FixtureCodableEnum)] = [(.tenMinutes, .oneHour), (.oneHour, .tenMinutes)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = .oneHour
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<FixtureCodableEnum?>("observeCodableEnumOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(FixtureCodableEnum?, FixtureCodableEnum?)] = [(nil, .tenMinutes), (.tenMinutes, .halfHour), (.halfHour, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = .tenMinutes
Defaults[key] = .halfHour
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[FixtureCodableEnum]>("observeCodableEnumArrayKeyCombine", default: [.tenMinutes])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(FixtureCodableEnum?, FixtureCodableEnum?)] = [(.tenMinutes, .halfHour), (.halfHour, .tenMinutes)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key][0] = .halfHour
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: FixtureCodableEnum]>("observeCodableEnumDictionaryKeyCombine", default: ["0": .tenMinutes])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(FixtureCodableEnum?, FixtureCodableEnum?)] = [(.tenMinutes, .halfHour), (.halfHour, .tenMinutes)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"])
XCTAssertEqual(expected.1, tuples[index].1["0"])
}
expect.fulfill()
}
Defaults[key]["0"] = .halfHour
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<FixtureCodableEnum>("observeCodableEnumKey", default: .tenMinutes)
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, .tenMinutes)
XCTAssertEqual(change.newValue, .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .halfHour
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<FixtureCodableEnum?>("observeCodableEnumOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue, .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .halfHour
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[FixtureCodableEnum]>("observeCodableEnumArrayKey", default: [.tenMinutes])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], .tenMinutes)
XCTAssertEqual(change.newValue[1], .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(.halfHour)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: FixtureCodableEnum]>("observeCodableEnumDictionaryKey", default: ["0": .tenMinutes])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"], .tenMinutes)
XCTAssertEqual(change.newValue["1"], .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = .halfHour
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,300 @@
import Foundation
import XCTest
import Defaults
private struct Unicorn: Codable, Defaults.Serializable {
var isUnicorn: Bool
}
private let fixtureCodable = Unicorn(isUnicorn: true)
extension Defaults.Keys {
fileprivate static let codable = Key<Unicorn>("codable", default: fixtureCodable)
fileprivate static let codableArray = Key<[Unicorn]>("codable", default: [fixtureCodable])
fileprivate static let codableDictionary = Key<[String: Unicorn]>("codable", default: ["0": fixtureCodable])
}
final class DefaultsCodableTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<Unicorn>("independentCodableKey", default: fixtureCodable)
XCTAssertTrue(Defaults[key].isUnicorn)
Defaults[key].isUnicorn = false
XCTAssertFalse(Defaults[key].isUnicorn)
}
func testOptionalKey() {
let key = Defaults.Key<Unicorn?>("independentCodableOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = Unicorn(isUnicorn: true)
XCTAssertTrue(Defaults[key]?.isUnicorn ?? false)
}
func testArrayKey() {
let key = Defaults.Key<[Unicorn]>("independentCodableArrayKey", default: [fixtureCodable])
XCTAssertTrue(Defaults[key][0].isUnicorn)
Defaults[key].append(Unicorn(isUnicorn: false))
XCTAssertTrue(Defaults[key][0].isUnicorn)
XCTAssertFalse(Defaults[key][1].isUnicorn)
}
func testArrayOptionalKey() {
let key = Defaults.Key<[Unicorn]?>("independentCodableArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [fixtureCodable]
Defaults[key]?.append(Unicorn(isUnicorn: false))
XCTAssertTrue(Defaults[key]?[0].isUnicorn ?? false)
XCTAssertFalse(Defaults[key]?[1].isUnicorn ?? false)
}
func testNestedArrayKey() {
let key = Defaults.Key<[[Unicorn]]>("independentCodableNestedArrayKey", default: [[fixtureCodable]])
XCTAssertTrue(Defaults[key][0][0].isUnicorn)
Defaults[key].append([fixtureCodable])
Defaults[key][0].append(Unicorn(isUnicorn: false))
XCTAssertTrue(Defaults[key][0][0].isUnicorn)
XCTAssertTrue(Defaults[key][1][0].isUnicorn)
XCTAssertFalse(Defaults[key][0][1].isUnicorn)
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: Unicorn]]>("independentCodableArrayDictionaryKey", default: [["0": fixtureCodable]])
XCTAssertTrue(Defaults[key][0]["0"]?.isUnicorn ?? false)
Defaults[key].append(["0": fixtureCodable])
Defaults[key][0]["1"] = Unicorn(isUnicorn: false)
XCTAssertTrue(Defaults[key][0]["0"]?.isUnicorn ?? false)
XCTAssertTrue(Defaults[key][1]["0"]?.isUnicorn ?? false)
XCTAssertFalse(Defaults[key][0]["1"]?.isUnicorn ?? true)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: Unicorn]>("independentCodableDictionaryKey", default: ["0": fixtureCodable])
XCTAssertTrue(Defaults[key]["0"]?.isUnicorn ?? false)
Defaults[key]["1"] = Unicorn(isUnicorn: false)
XCTAssertTrue(Defaults[key]["0"]?.isUnicorn ?? false)
XCTAssertFalse(Defaults[key]["1"]?.isUnicorn ?? true)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: Unicorn]?>("independentCodableDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": fixtureCodable]
Defaults[key]?["1"] = Unicorn(isUnicorn: false)
XCTAssertTrue(Defaults[key]?["0"]?.isUnicorn ?? false)
XCTAssertFalse(Defaults[key]?["1"]?.isUnicorn ?? true)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [Unicorn]]>("independentCodableDictionaryArrayKey", default: ["0": [fixtureCodable]])
XCTAssertTrue(Defaults[key]["0"]?[0].isUnicorn ?? false)
Defaults[key]["1"] = [fixtureCodable]
Defaults[key]["0"]?.append(Unicorn(isUnicorn: false))
XCTAssertTrue(Defaults[key]["1"]?[0].isUnicorn ?? false)
XCTAssertFalse(Defaults[key]["0"]?[1].isUnicorn ?? true)
}
func testType() {
XCTAssertTrue(Defaults[.codable].isUnicorn)
Defaults[.codable] = Unicorn(isUnicorn: false)
XCTAssertFalse(Defaults[.codable].isUnicorn)
}
func testArrayType() {
XCTAssertTrue(Defaults[.codableArray][0].isUnicorn)
Defaults[.codableArray][0] = Unicorn(isUnicorn: false)
XCTAssertFalse(Defaults[.codableArray][0].isUnicorn)
}
func testDictionaryType() {
XCTAssertTrue(Defaults[.codableDictionary]["0"]?.isUnicorn ?? false)
Defaults[.codableDictionary]["0"] = Unicorn(isUnicorn: false)
XCTAssertFalse(Defaults[.codableDictionary]["0"]?.isUnicorn ?? true)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<Unicorn>("observeCodableKeyCombine", default: fixtureCodable)
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue.isUnicorn, $0.newValue.isUnicorn) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(true, false), (false, true)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = Unicorn(isUnicorn: false)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<Unicorn?>("observeCodableOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue?.isUnicorn, $0.newValue?.isUnicorn) }
.collect(2)
let expectedValue: [(Bool?, Bool?)] = [(nil, true), (true, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = fixtureCodable
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[Unicorn]>("observeCodableArrayKeyCombine", default: [fixtureCodable])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(true, false), (false, true)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0].isUnicorn)
XCTAssertEqual(expected.1, tuples[index].1[0].isUnicorn)
}
expect.fulfill()
}
Defaults[key][0] = Unicorn(isUnicorn: false)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: Unicorn]>("observeCodableDictionaryKeyCombine", default: ["0": fixtureCodable])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(true, false), (false, true)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"]?.isUnicorn)
XCTAssertEqual(expected.1, tuples[index].1["0"]?.isUnicorn)
}
expect.fulfill()
}
Defaults[key]["0"] = Unicorn(isUnicorn: false)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<Unicorn>("observeCodableKey", default: fixtureCodable)
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue.isUnicorn)
XCTAssertFalse(change.newValue.isUnicorn)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = Unicorn(isUnicorn: false)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<Unicorn?>("observeCodableOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertTrue(change.newValue?.isUnicorn ?? false)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureCodable
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[Unicorn]>("observeCodableArrayKey", default: [fixtureCodable])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue[0].isUnicorn)
XCTAssertFalse(change.newValue[0].isUnicorn)
observation.invalidate()
expect.fulfill()
}
Defaults[key][0] = Unicorn(isUnicorn: false)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: Unicorn]>("observeCodableDictionaryKey", default: ["0": fixtureCodable])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue["0"]?.isUnicorn ?? false)
XCTAssertFalse(change.newValue["0"]?.isUnicorn ?? true)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["0"] = Unicorn(isUnicorn: false)
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,351 @@
import Foundation
import XCTest
import Defaults
private struct Item: Equatable {
let name: String
let count: UInt
}
extension Item: Defaults.Serializable {
static let bridge = ItemBridge()
}
private struct ItemBridge: Defaults.Bridge {
typealias Value = Item
typealias Serializable = [String: String]
func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return ["name": value.name, "count": String(value.count)]
}
func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let name = object["name"],
let count = UInt(object["count"] ?? "0")
else {
return nil
}
return Value(name: name, count: count)
}
}
private let fixtureCustomCollection = Item(name: "Apple", count: 10)
private let fixtureCustomCollection1 = Item(name: "Banana", count: 20)
private let fixtureCustomCollection2 = Item(name: "Grape", count: 30)
extension Defaults.Keys {
fileprivate static let collectionCustomElement = Key<Bag<Item>>("collectionCustomElement", default: .init(items: [fixtureCustomCollection]))
fileprivate static let collectionCustomElementArray = Key<[Bag<Item>]>("collectionCustomElementArray", default: [.init(items: [fixtureCustomCollection])])
fileprivate static let collectionCustomElementDictionary = Key<[String: Bag<Item>]>("collectionCustomElementDictionary", default: ["0": .init(items: [fixtureCustomCollection])])
}
final class DefaultsCollectionCustomElementTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<Bag<Item>>("independentCollectionCustomElementKey", default: .init(items: [fixtureCustomCollection]))
Defaults[key].insert(element: fixtureCustomCollection1, at: 1)
Defaults[key].insert(element: fixtureCustomCollection2, at: 2)
XCTAssertEqual(Defaults[key][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key][1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key][2], fixtureCustomCollection2)
}
func testOptionalKey() {
let key = Defaults.Key<Bag<Item>?>("independentCollectionCustomElementOptionalKey")
Defaults[key] = .init(items: [fixtureCustomCollection])
Defaults[key]?.insert(element: fixtureCustomCollection1, at: 1)
Defaults[key]?.insert(element: fixtureCustomCollection2, at: 2)
XCTAssertEqual(Defaults[key]?[0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key]?[1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key]?[2], fixtureCustomCollection2)
}
func testArrayKey() {
let key = Defaults.Key<[Bag<Item>]>("independentCollectionCustomElementArrayKey", default: [.init(items: [fixtureCustomCollection])])
Defaults[key][0].insert(element: fixtureCustomCollection1, at: 1)
Defaults[key].append(.init(items: [fixtureCustomCollection2]))
XCTAssertEqual(Defaults[key][0][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key][0][1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key][1][0], fixtureCustomCollection2)
}
func testArrayOptionalKey() {
let key = Defaults.Key<[Bag<Item>]?>("independentCollectionCustomElementArrayOptionalKey")
Defaults[key] = [.init(items: [fixtureCustomCollection])]
Defaults[key]?[0].insert(element: fixtureCustomCollection1, at: 1)
Defaults[key]?.append(Bag(items: [fixtureCustomCollection2]))
XCTAssertEqual(Defaults[key]?[0][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key]?[0][1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key]?[1][0], fixtureCustomCollection2)
}
func testNestedArrayKey() {
let key = Defaults.Key<[[Bag<Item>]]>("independentCollectionCustomElementNestedArrayKey", default: [[.init(items: [fixtureCustomCollection])]])
Defaults[key][0][0].insert(element: fixtureCustomCollection, at: 1)
Defaults[key][0].append(.init(items: [fixtureCustomCollection1]))
Defaults[key].append([.init(items: [fixtureCustomCollection2])])
XCTAssertEqual(Defaults[key][0][0][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key][0][0][1], fixtureCustomCollection)
XCTAssertEqual(Defaults[key][0][1][0], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key][1][0][0], fixtureCustomCollection2)
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: Bag<Item>]]>("independentCollectionCustomElementArrayDictionaryKey", default: [["0": .init(items: [fixtureCustomCollection])]])
Defaults[key][0]["0"]?.insert(element: fixtureCustomCollection, at: 1)
Defaults[key][0]["1"] = .init(items: [fixtureCustomCollection1])
Defaults[key].append(["0": .init(items: [fixtureCustomCollection2])])
XCTAssertEqual(Defaults[key][0]["0"]?[0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key][0]["0"]?[1], fixtureCustomCollection)
XCTAssertEqual(Defaults[key][0]["1"]?[0], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key][1]["0"]?[0], fixtureCustomCollection2)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: Bag<Item>]>("independentCollectionCustomElementDictionaryKey", default: ["0": .init(items: [fixtureCustomCollection])])
Defaults[key]["0"]?.insert(element: fixtureCustomCollection1, at: 1)
Defaults[key]["1"] = .init(items: [fixtureCustomCollection2])
XCTAssertEqual(Defaults[key]["0"]?[0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key]["1"]?[0], fixtureCustomCollection2)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: Bag<Item>]?>("independentCollectionCustomElementDictionaryOptionalKey")
Defaults[key] = ["0": .init(items: [fixtureCustomCollection])]
Defaults[key]?["0"]?.insert(element: fixtureCustomCollection1, at: 1)
Defaults[key]?["1"] = .init(items: [fixtureCustomCollection2])
XCTAssertEqual(Defaults[key]?["0"]?[0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key]?["0"]?[1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key]?["1"]?[0], fixtureCustomCollection2)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [Bag<Item>]]>("independentCollectionCustomElementDictionaryArrayKey", default: ["0": [.init(items: [fixtureCustomCollection])]])
Defaults[key]["0"]?[0].insert(element: fixtureCustomCollection, at: 1)
Defaults[key]["0"]?.append(.init(items: [fixtureCustomCollection1]))
Defaults[key]["1"] = [.init(items: [fixtureCustomCollection2])]
XCTAssertEqual(Defaults[key]["0"]?[0][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[key]["0"]?[0][1], fixtureCustomCollection)
XCTAssertEqual(Defaults[key]["0"]?[1][0], fixtureCustomCollection1)
XCTAssertEqual(Defaults[key]["1"]?[0][0], fixtureCustomCollection2)
}
func testType() {
Defaults[.collectionCustomElement].insert(element: fixtureCustomCollection1, at: 1)
Defaults[.collectionCustomElement].insert(element: fixtureCustomCollection2, at: 2)
XCTAssertEqual(Defaults[.collectionCustomElement][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[.collectionCustomElement][1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[.collectionCustomElement][2], fixtureCustomCollection2)
}
func testArrayType() {
Defaults[.collectionCustomElementArray][0].insert(element: fixtureCustomCollection1, at: 1)
Defaults[.collectionCustomElementArray].append(.init(items: [fixtureCustomCollection2]))
XCTAssertEqual(Defaults[.collectionCustomElementArray][0][0], fixtureCustomCollection)
XCTAssertEqual(Defaults[.collectionCustomElementArray][0][1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[.collectionCustomElementArray][1][0], fixtureCustomCollection2)
}
func testDictionaryType() {
Defaults[.collectionCustomElementDictionary]["0"]?.insert(element: fixtureCustomCollection1, at: 1)
Defaults[.collectionCustomElementDictionary]["1"] = .init(items: [fixtureCustomCollection2])
XCTAssertEqual(Defaults[.collectionCustomElementDictionary]["0"]?[0], fixtureCustomCollection)
XCTAssertEqual(Defaults[.collectionCustomElementDictionary]["0"]?[1], fixtureCustomCollection1)
XCTAssertEqual(Defaults[.collectionCustomElementDictionary]["1"]?[0], fixtureCustomCollection2)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<Bag<Item>>("observeCollectionCustomElementKeyCombine", default: .init(items: [fixtureCustomCollection]))
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, fixtureCustomCollection)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key].insert(element: fixtureCustomCollection1, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<Bag<Item>?>("observeCollectionCustomElementOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(Item?, Item?)] = [(nil, fixtureCustomCollection), (fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0?[0])
XCTAssertEqual(expected.1, tuples[index].1?[0])
}
expect.fulfill()
}
Defaults[key] = .init(items: [fixtureCustomCollection])
Defaults[key]?.insert(element: fixtureCustomCollection1, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[Bag<Item>]>("observeCollectionCustomElementArrayKeyCombine", default: [.init(items: [fixtureCustomCollection])])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, fixtureCustomCollection)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0][0])
XCTAssertEqual(expected.1, tuples[index].1[0][0])
}
expect.fulfill()
}
Defaults[key][0].insert(element: fixtureCustomCollection1, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: Bag<Item>]>("observeCollectionCustomElementDictionaryKeyCombine", default: ["0": .init(items: [fixtureCustomCollection])])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCustomCollection, fixtureCustomCollection1), (fixtureCustomCollection1, fixtureCustomCollection)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"]?[0])
XCTAssertEqual(expected.1, tuples[index].1["0"]?[0])
}
expect.fulfill()
}
Defaults[key]["0"]?.insert(element: fixtureCustomCollection1, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<Bag<Item>>("observeCollectionCustomElementKey", default: .init(items: [fixtureCustomCollection]))
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], fixtureCustomCollection)
XCTAssertEqual(change.newValue[0], fixtureCustomCollection1)
observation.invalidate()
expect.fulfill()
}
Defaults[key].insert(element: fixtureCustomCollection1, at: 0)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<Bag<Item>?>("observeCollectionCustomElementOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue?[0], fixtureCustomCollection)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .init(items: [fixtureCustomCollection])
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[Bag<Item>]>("observeCollectionCustomElementArrayKey", default: [.init(items: [fixtureCustomCollection])])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0][0], fixtureCustomCollection)
XCTAssertEqual(change.newValue[0][0], fixtureCustomCollection1)
observation.invalidate()
expect.fulfill()
}
Defaults[key][0].insert(element: fixtureCustomCollection1, at: 0)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: Bag<Item>]>("observeCollectionCustomElementArrayKey", default: ["0": .init(items: [fixtureCustomCollection])])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"]?[0], fixtureCustomCollection)
XCTAssertEqual(change.newValue["0"]?[0], fixtureCustomCollection1)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["0"]?.insert(element: fixtureCustomCollection1, at: 0)
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,344 @@
import Foundation
import XCTest
import Defaults
struct Bag<Element: Defaults.Serializable>: Collection {
var items: [Element]
init(items: [Element]) {
self.items = items
}
var startIndex: Int {
items.startIndex
}
var endIndex: Int {
items.endIndex
}
mutating func insert(element: Element, at: Int) {
items.insert(element, at: at)
}
func index(after index: Int) -> Int {
items.index(after: index)
}
subscript(position: Int) -> Element {
items[position]
}
}
extension Bag: Defaults.CollectionSerializable {
init(_ elements: [Element]) {
self.items = elements
}
}
private let fixtureCollection = ["Juice", "Apple", "Banana"]
extension Defaults.Keys {
fileprivate static let collection = Key<Bag<String>>("collection", default: Bag(items: fixtureCollection))
fileprivate static let collectionArray = Key<[Bag<String>]>("collectionArray", default: [Bag(items: fixtureCollection)])
fileprivate static let collectionDictionary = Key<[String: Bag<String>]>("collectionDictionary", default: ["0": Bag(items: fixtureCollection)])
}
final class DefaultsCollectionTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<Bag<String>>("independentCollectionKey", default: Bag(items: fixtureCollection))
Defaults[key].insert(element: "123", at: 0)
XCTAssertEqual(Defaults[key][0], "123")
}
func testOptionalKey() {
let key = Defaults.Key<Bag<String>?>("independentCollectionOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = Bag(items: [])
Defaults[key]?.insert(element: fixtureCollection[0], at: 0)
XCTAssertEqual(Defaults[key]?[0], fixtureCollection[0])
Defaults[key]?.insert(element: fixtureCollection[1], at: 1)
XCTAssertEqual(Defaults[key]?[1], fixtureCollection[1])
}
func testArrayKey() {
let key = Defaults.Key<[Bag<String>]>("independentCollectionArrayKey", default: [Bag(items: [fixtureCollection[0]])])
Defaults[key].append(Bag(items: [fixtureCollection[1]]))
XCTAssertEqual(Defaults[key][1][0], fixtureCollection[1])
Defaults[key][0].insert(element: fixtureCollection[2], at: 1)
XCTAssertEqual(Defaults[key][0][1], fixtureCollection[2])
}
func testArrayOptionalKey() {
let key = Defaults.Key<[Bag<String>]?>("independentCollectionArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [Bag(items: [fixtureCollection[0]])]
Defaults[key]?.append(Bag(items: [fixtureCollection[1]]))
XCTAssertEqual(Defaults[key]?[1][0], fixtureCollection[1])
Defaults[key]?[0].insert(element: fixtureCollection[2], at: 1)
XCTAssertEqual(Defaults[key]?[0][1], fixtureCollection[2])
}
func testNestedArrayKey() {
let key = Defaults.Key<[[Bag<String>]]>("independentCollectionNestedArrayKey", default: [[Bag(items: [fixtureCollection[0]])]])
Defaults[key][0].append(Bag(items: [fixtureCollection[1]]))
Defaults[key].append([Bag(items: [fixtureCollection[2]])])
XCTAssertEqual(Defaults[key][0][0][0], fixtureCollection[0])
XCTAssertEqual(Defaults[key][0][1][0], fixtureCollection[1])
XCTAssertEqual(Defaults[key][1][0][0], fixtureCollection[2])
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: Bag<String>]]>("independentCollectionArrayDictionaryKey", default: [["0": Bag(items: [fixtureCollection[0]])]])
Defaults[key][0]["1"] = Bag(items: [fixtureCollection[1]])
Defaults[key].append(["0": Bag(items: [fixtureCollection[2]])])
XCTAssertEqual(Defaults[key][0]["0"]?[0], fixtureCollection[0])
XCTAssertEqual(Defaults[key][0]["1"]?[0], fixtureCollection[1])
XCTAssertEqual(Defaults[key][1]["0"]?[0], fixtureCollection[2])
}
func testDictionaryKey() {
let key = Defaults.Key<[String: Bag<String>]>("independentCollectionDictionaryKey", default: ["0": Bag(items: [fixtureCollection[0]])])
Defaults[key]["0"]?.insert(element: fixtureCollection[1], at: 1)
Defaults[key]["1"] = Bag(items: [fixtureCollection[2]])
XCTAssertEqual(Defaults[key]["0"]?[0], fixtureCollection[0])
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCollection[1])
XCTAssertEqual(Defaults[key]["1"]?[0], fixtureCollection[2])
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: Bag<String>]?>("independentCollectionDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": Bag(items: [fixtureCollection[0]])]
Defaults[key]?["0"]?.insert(element: fixtureCollection[1], at: 1)
Defaults[key]?["1"] = Bag(items: [fixtureCollection[2]])
XCTAssertEqual(Defaults[key]?["0"]?[0], fixtureCollection[0])
XCTAssertEqual(Defaults[key]?["0"]?[1], fixtureCollection[1])
XCTAssertEqual(Defaults[key]?["1"]?[0], fixtureCollection[2])
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [Bag<String>]]>("independentCollectionDictionaryArrayKey", default: ["0": [Bag(items: [fixtureCollection[0]])]])
Defaults[key]["0"]?[0].insert(element: fixtureCollection[1], at: 1)
Defaults[key]["1"] = [Bag(items: [fixtureCollection[2]])]
XCTAssertEqual(Defaults[key]["0"]?[0][0], fixtureCollection[0])
XCTAssertEqual(Defaults[key]["0"]?[0][1], fixtureCollection[1])
XCTAssertEqual(Defaults[key]["1"]?[0][0], fixtureCollection[2])
}
func testType() {
Defaults[.collection].insert(element: "123", at: 0)
XCTAssertEqual(Defaults[.collection][0], "123")
}
func testArrayType() {
Defaults[.collectionArray].append(Bag(items: [fixtureCollection[0]]))
Defaults[.collectionArray][0].insert(element: "123", at: 0)
XCTAssertEqual(Defaults[.collectionArray][0][0], "123")
XCTAssertEqual(Defaults[.collectionArray][1][0], fixtureCollection[0])
}
func testDictionaryType() {
Defaults[.collectionDictionary]["1"] = Bag(items: [fixtureCollection[0]])
Defaults[.collectionDictionary]["0"]?.insert(element: "123", at: 0)
XCTAssertEqual(Defaults[.collectionDictionary]["0"]?[0], "123")
XCTAssertEqual(Defaults[.collectionDictionary]["1"]?[0], fixtureCollection[0])
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<Bag<String>>("observeCollectionKeyCombine", default: .init(items: fixtureCollection))
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCollection[0], item), (item, fixtureCollection[0])].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key].insert(element: item, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<Bag<String>?>("observeCollectionOptionalKeyCombine")
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(String?, String?)] = [(nil, fixtureCollection[0]), (fixtureCollection[0], item), (item, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0?[0])
XCTAssertEqual(expected.1, tuples[index].1?[0])
}
expect.fulfill()
}
Defaults[key] = Bag(items: fixtureCollection)
Defaults[key]?.insert(element: item, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[Bag<String>]>("observeCollectionArrayKeyCombine", default: [.init(items: fixtureCollection)])
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCollection[0], item), (item, fixtureCollection[0])].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0][0])
XCTAssertEqual(expected.1, tuples[index].1[0][0])
}
expect.fulfill()
}
Defaults[key][0].insert(element: item, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: Bag<String>]>("observeCollectionArrayKeyCombine", default: ["0": .init(items: fixtureCollection)])
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCollection[0], item), (item, fixtureCollection[0])].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"]?[0])
XCTAssertEqual(expected.1, tuples[index].1["0"]?[0])
}
expect.fulfill()
}
Defaults[key]["0"]?.insert(element: item, at: 0)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<Bag<String>>("observeCollectionKey", default: .init(items: fixtureCollection))
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], fixtureCollection[0])
XCTAssertEqual(change.newValue[0], item)
observation.invalidate()
expect.fulfill()
}
Defaults[key].insert(element: item, at: 0)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<Bag<String>?>("observeCollectionOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue?[0], fixtureCollection[0])
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .init(items: fixtureCollection)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[Bag<String>]>("observeCollectionArrayKey", default: [.init(items: fixtureCollection)])
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0][0], fixtureCollection[0])
XCTAssertEqual(change.newValue[0][0], item)
observation.invalidate()
expect.fulfill()
}
Defaults[key][0].insert(element: item, at: 0)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: Bag<String>]>("observeCollectionDictionaryKey", default: ["0": .init(items: fixtureCollection)])
let item = "Grape"
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"]?[0], fixtureCollection[0])
XCTAssertEqual(change.newValue["0"]?[0], item)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["0"]?.insert(element: item, at: 0)
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,357 @@
import Foundation
import Defaults
import XCTest
public struct User: Hashable, Equatable {
var username: String
var password: String
}
extension User: Defaults.Serializable {
public static let bridge = DefaultsUserBridge()
}
public final class DefaultsUserBridge: Defaults.Bridge {
public typealias Value = User
public typealias Serializable = [String: String]
public func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return ["username": value.username, "password": value.password]
}
public func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let username = object["username"],
let password = object["password"]
else {
return nil
}
return User(username: username, password: password)
}
}
private let fixtureCustomBridge = User(username: "hank121314", password: "123456")
extension Defaults.Keys {
fileprivate static let customBridge = Key<User>("customBridge", default: fixtureCustomBridge)
fileprivate static let customBridgeArray = Key<[User]>("array_customBridge", default: [fixtureCustomBridge])
fileprivate static let customBridgeDictionary = Key<[String: User]>("dictionary_customBridge", default: ["0": fixtureCustomBridge])
}
final class DefaultsCustomBridge: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<User>("independentCustomBridgeKey", default: fixtureCustomBridge)
XCTAssertEqual(Defaults[key], fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[key] = newUser
XCTAssertEqual(Defaults[key], newUser)
}
func testOptionalKey() {
let key = Defaults.Key<User?>("independentCustomBridgeOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = fixtureCustomBridge
XCTAssertEqual(Defaults[key], fixtureCustomBridge)
}
func testArrayKey() {
let user = User(username: "hank121314", password: "123456")
let key = Defaults.Key<[User]>("independentCustomBridgeArrayKey", default: [user])
XCTAssertEqual(Defaults[key][0], user)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[key][0] = newUser
XCTAssertEqual(Defaults[key][0], newUser)
}
func testArrayOptionalKey() {
let key = Defaults.Key<[User]?>("independentCustomBridgeArrayOptionalKey")
XCTAssertNil(Defaults[key])
let newUser = User(username: "hank121314", password: "123456")
Defaults[key] = [newUser]
XCTAssertEqual(Defaults[key]?[0], newUser)
Defaults[key] = nil
XCTAssertNil(Defaults[key])
}
func testNestedArrayKey() {
let key = Defaults.Key<[[User]]>("independentCustomBridgeNestedArrayKey", default: [[fixtureCustomBridge], [fixtureCustomBridge]])
XCTAssertEqual(Defaults[key][0][0].username, fixtureCustomBridge.username)
let newUsername = "John"
let newPassword = "7891011"
Defaults[key][0][0] = User(username: newUsername, password: newPassword)
XCTAssertEqual(Defaults[key][0][0].username, newUsername)
XCTAssertEqual(Defaults[key][0][0].password, newPassword)
XCTAssertEqual(Defaults[key][1][0].username, fixtureCustomBridge.username)
XCTAssertEqual(Defaults[key][1][0].password, fixtureCustomBridge.password)
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: User]]>("independentCustomBridgeArrayDictionaryKey", default: [["0": fixtureCustomBridge], ["0": fixtureCustomBridge]])
XCTAssertEqual(Defaults[key][0]["0"]?.username, fixtureCustomBridge.username)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[key][0]["0"] = newUser
XCTAssertEqual(Defaults[key][0]["0"], newUser)
XCTAssertEqual(Defaults[key][1]["0"], fixtureCustomBridge)
}
func testSetKey() {
let key = Defaults.Key<Set<User>>("independentCustomBridgeSetKey", default: [fixtureCustomBridge])
XCTAssertEqual(Defaults[key].first, fixtureCustomBridge)
Defaults[key].insert(fixtureCustomBridge)
XCTAssertEqual(Defaults[key].count, 1)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[key].insert(newUser)
XCTAssertTrue(Defaults[key].contains(newUser))
}
func testDictionaryKey() {
let key = Defaults.Key<[String: User]>("independentCustomBridgeDictionaryKey", default: ["0": fixtureCustomBridge])
XCTAssertEqual(Defaults[key]["0"], fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[key]["0"] = newUser
XCTAssertEqual(Defaults[key]["0"], newUser)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: User]?>("independentCustomBridgeDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": fixtureCustomBridge]
XCTAssertEqual(Defaults[key]?["0"], fixtureCustomBridge)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [User]]>("independentCustomBridgeDictionaryArrayKey", default: ["0": [fixtureCustomBridge]])
XCTAssertEqual(Defaults[key]["0"]?[0], fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[key]["0"]?[0] = newUser
Defaults[key]["0"]?.append(fixtureCustomBridge)
XCTAssertEqual(Defaults[key]["0"]?[0], newUser)
XCTAssertEqual(Defaults[key]["0"]?[0], newUser)
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomBridge)
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomBridge)
}
func testType() {
XCTAssertEqual(Defaults[.customBridge], fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[.customBridge] = newUser
XCTAssertEqual(Defaults[.customBridge], newUser)
}
func testArrayType() {
XCTAssertEqual(Defaults[.customBridgeArray][0], fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[.customBridgeArray][0] = newUser
XCTAssertEqual(Defaults[.customBridgeArray][0], newUser)
}
func testDictionaryType() {
XCTAssertEqual(Defaults[.customBridgeDictionary]["0"], fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
Defaults[.customBridgeDictionary]["0"] = newUser
XCTAssertEqual(Defaults[.customBridgeDictionary]["0"], newUser)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<User>("observeCustomBridgeKeyCombine", default: fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCustomBridge, newUser), (newUser, fixtureCustomBridge)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = newUser
Defaults[key] = fixtureCustomBridge
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<User?>("observeCustomBridgeOptionalKeyCombine")
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(User?, User?)] = [(nil, fixtureCustomBridge), (fixtureCustomBridge, newUser), (newUser, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = fixtureCustomBridge
Defaults[key] = newUser
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[User]>("observeCustomBridgeArrayKeyCombine", default: [fixtureCustomBridge])
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [([fixtureCustomBridge], [newUser]), ([newUser], [newUser, fixtureCustomBridge])].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key][0] = newUser
Defaults[key].append(fixtureCustomBridge)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryCombine() {
let key = Defaults.Key<[String: User]>("observeCustomBridgeDictionaryKeyCombine", default: ["0": fixtureCustomBridge])
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureCustomBridge, newUser), (newUser, fixtureCustomBridge)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"])
XCTAssertEqual(expected.1, tuples[index].1["0"])
}
expect.fulfill()
}
Defaults[key]["0"] = newUser
Defaults[key]["0"] = fixtureCustomBridge
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<User>("observeCustomBridgeKey", default: fixtureCustomBridge)
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, fixtureCustomBridge)
XCTAssertEqual(change.newValue, newUser)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = newUser
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<User?>("observeCustomBridgeOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue, fixtureCustomBridge)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureCustomBridge
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[User]>("observeCustomBridgeArrayKey", default: [fixtureCustomBridge])
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], fixtureCustomBridge)
XCTAssertEqual(change.newValue[0], newUser)
observation.invalidate()
expect.fulfill()
}
Defaults[key][0] = newUser
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: User]>("observeCustomBridgeDictionaryKey", default: ["0": fixtureCustomBridge])
let newUser = User(username: "sindresorhus", password: "123456789")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"], fixtureCustomBridge)
XCTAssertEqual(change.newValue["0"], newUser)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["0"] = newUser
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,160 @@
import Foundation
import Defaults
import XCTest
private let fixtureDictionary = ["0": "Hank"]
private let fixtureArray = ["Hank", "Chen"]
extension Defaults.Keys {
fileprivate static let dictionary = Key<[String: String]>("dictionary", default: fixtureDictionary)
}
final class DefaultsDictionaryTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<[String: String]>("independentDictionaryStringKey", default: fixtureDictionary)
XCTAssertEqual(Defaults[key]["0"], fixtureDictionary["0"])
let newValue = "John"
Defaults[key]["0"] = newValue
XCTAssertEqual(Defaults[key]["0"], newValue)
}
func testOptionalKey() {
let key = Defaults.Key<[String: String]?>("independentDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = fixtureDictionary
XCTAssertEqual(Defaults[key]?["0"], fixtureDictionary["0"])
Defaults[key] = nil
XCTAssertNil(Defaults[key])
let newValue = ["0": "Chen"]
Defaults[key] = newValue
XCTAssertEqual(Defaults[key]?["0"], newValue["0"])
}
func testNestedKey() {
let key = Defaults.Key<[String: [String: String]]>("independentDictionaryNestedKey", default: ["0": fixtureDictionary])
XCTAssertEqual(Defaults[key]["0"]?["0"], "Hank")
let newName = "Chen"
Defaults[key]["0"]?["0"] = newName
XCTAssertEqual(Defaults[key]["0"]?["0"], newName)
}
func testArrayKey() {
let key = Defaults.Key<[String: [String]]>("independentDictionaryArrayKey", default: ["0": fixtureArray])
XCTAssertEqual(Defaults[key]["0"], fixtureArray)
let newName = "Chen"
Defaults[key]["0"]?[0] = newName
XCTAssertEqual(Defaults[key]["0"], [newName, fixtureArray[1]])
}
func testType() {
XCTAssertEqual(Defaults[.dictionary]["0"], fixtureDictionary["0"])
let newName = "Chen"
Defaults[.dictionary]["0"] = newName
XCTAssertEqual(Defaults[.dictionary]["0"], newName)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<[String: String]>("observeDictionaryKeyCombine", default: fixtureDictionary)
let expect = expectation(description: "Observation closure being called")
let newName = "John"
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureDictionary["0"]!, newName), (newName, fixtureDictionary["0"]!)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"])
XCTAssertEqual(expected.1, tuples[index].1["0"])
}
expect.fulfill()
}
Defaults[key]["0"] = newName
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<[String: String]?>("observeDictionaryOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let newName = ["0": "John"]
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
// swiftlint:disable discouraged_optional_collection
let expectedValues: [([String: String]?, [String: String]?)] = [(nil, fixtureDictionary), (fixtureDictionary, newName), (newName, nil)]
let cancellable = publisher.sink { actualValues in
for (expected, actual) in zip(expectedValues, actualValues) {
XCTAssertEqual(expected.0, actual.0)
XCTAssertEqual(expected.1, actual.1)
}
expect.fulfill()
}
Defaults[key] = fixtureDictionary
Defaults[key] = newName
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<[String: String]>("observeDictionaryKey", default: fixtureDictionary)
let expect = expectation(description: "Observation closure being called")
let newName = "John"
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, fixtureDictionary)
XCTAssertEqual(change.newValue["1"], newName)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = newName
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<[String: String]?>("observeDictionaryOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue!, fixtureDictionary)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureDictionary
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,308 @@
import Foundation
import Defaults
import XCTest
private enum FixtureEnum: String, Defaults.Serializable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension Defaults.Keys {
fileprivate static let `enum` = Key<FixtureEnum>("enum", default: .tenMinutes)
fileprivate static let enumArray = Key<[FixtureEnum]>("array_enum", default: [.tenMinutes])
fileprivate static let enumDictionary = Key<[String: FixtureEnum]>("dictionary_enum", default: ["0": .tenMinutes])
}
final class DefaultsEnumTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<FixtureEnum>("independentEnumKey", default: .tenMinutes)
XCTAssertEqual(Defaults[key], .tenMinutes)
Defaults[key] = .halfHour
XCTAssertEqual(Defaults[key], .halfHour)
}
func testOptionalKey() {
let key = Defaults.Key<FixtureEnum?>("independentEnumOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = .tenMinutes
XCTAssertEqual(Defaults[key], .tenMinutes)
}
func testArrayKey() {
let key = Defaults.Key<[FixtureEnum]>("independentEnumArrayKey", default: [.tenMinutes])
XCTAssertEqual(Defaults[key][0], .tenMinutes)
Defaults[key].append(.halfHour)
XCTAssertEqual(Defaults[key][0], .tenMinutes)
XCTAssertEqual(Defaults[key][1], .halfHour)
}
func testArrayOptionalKey() {
let key = Defaults.Key<[FixtureEnum]?>("independentEnumArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [.tenMinutes]
Defaults[key]?.append(.halfHour)
XCTAssertEqual(Defaults[key]?[0], .tenMinutes)
XCTAssertEqual(Defaults[key]?[1], .halfHour)
}
func testNestedArrayKey() {
let key = Defaults.Key<[[FixtureEnum]]>("independentEnumNestedArrayKey", default: [[.tenMinutes]])
XCTAssertEqual(Defaults[key][0][0], .tenMinutes)
Defaults[key][0].append(.halfHour)
Defaults[key].append([.oneHour])
XCTAssertEqual(Defaults[key][0][1], .halfHour)
XCTAssertEqual(Defaults[key][1][0], .oneHour)
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: FixtureEnum]]>("independentEnumArrayDictionaryKey", default: [["0": .tenMinutes]])
XCTAssertEqual(Defaults[key][0]["0"], .tenMinutes)
Defaults[key][0]["1"] = .halfHour
Defaults[key].append(["0": .oneHour])
XCTAssertEqual(Defaults[key][0]["1"], .halfHour)
XCTAssertEqual(Defaults[key][1]["0"], .oneHour)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: FixtureEnum]>("independentEnumDictionaryKey", default: ["0": .tenMinutes])
XCTAssertEqual(Defaults[key]["0"], .tenMinutes)
Defaults[key]["1"] = .halfHour
XCTAssertEqual(Defaults[key]["0"], .tenMinutes)
XCTAssertEqual(Defaults[key]["1"], .halfHour)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: FixtureEnum]?>("independentEnumDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": .tenMinutes]
XCTAssertEqual(Defaults[key]?["0"], .tenMinutes)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [FixtureEnum]]>("independentEnumDictionaryKey", default: ["0": [.tenMinutes]])
XCTAssertEqual(Defaults[key]["0"]?[0], .tenMinutes)
Defaults[key]["0"]?.append(.halfHour)
Defaults[key]["1"] = [.oneHour]
XCTAssertEqual(Defaults[key]["0"]?[1], .halfHour)
XCTAssertEqual(Defaults[key]["1"]?[0], .oneHour)
}
func testType() {
XCTAssertEqual(Defaults[.enum], .tenMinutes)
Defaults[.enum] = .halfHour
XCTAssertEqual(Defaults[.enum], .halfHour)
}
func testArrayType() {
XCTAssertEqual(Defaults[.enumArray][0], .tenMinutes)
Defaults[.enumArray][0] = .oneHour
XCTAssertEqual(Defaults[.enumArray][0], .oneHour)
}
func testDictionaryType() {
XCTAssertEqual(Defaults[.enumDictionary]["0"], .tenMinutes)
Defaults[.enumDictionary]["0"] = .halfHour
XCTAssertEqual(Defaults[.enumDictionary]["0"], .halfHour)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<FixtureEnum>("observeEnumKeyCombine", default: .tenMinutes)
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(FixtureEnum, FixtureEnum)] = [(.tenMinutes, .halfHour), (.halfHour, .oneHour), (.oneHour, .tenMinutes)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = .tenMinutes
Defaults[key] = .halfHour
Defaults[key] = .oneHour
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<FixtureEnum?>("observeEnumOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(4)
let expectedValue: [(FixtureEnum?, FixtureEnum?)] = [(nil, .tenMinutes), (.tenMinutes, .halfHour), (.halfHour, .oneHour), (.oneHour, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = .tenMinutes
Defaults[key] = .halfHour
Defaults[key] = .oneHour
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[FixtureEnum]>("observeEnumArrayKeyCombine", default: [.tenMinutes])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(FixtureEnum, FixtureEnum)] = [(.tenMinutes, .halfHour), (.halfHour, .oneHour)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key][0] = .halfHour
Defaults[key][0] = .oneHour
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: FixtureEnum]>("observeEnumDictionaryKeyCombine", default: ["0": .tenMinutes])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(FixtureEnum, FixtureEnum)] = [(.tenMinutes, .halfHour), (.halfHour, .oneHour)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"])
XCTAssertEqual(expected.1, tuples[index].1["0"])
}
expect.fulfill()
}
Defaults[key]["0"] = .halfHour
Defaults[key]["0"] = .oneHour
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<FixtureEnum>("observeEnumKey", default: .tenMinutes)
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, .tenMinutes)
XCTAssertEqual(change.newValue, .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .halfHour
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<FixtureEnum?>("observeEnumOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue, .tenMinutes)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .tenMinutes
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[FixtureEnum]>("observeEnumArrayKey", default: [.tenMinutes])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], .tenMinutes)
XCTAssertEqual(change.newValue[1], .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(.halfHour)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: FixtureEnum]>("observeEnumDictionaryKey", default: ["0": .tenMinutes])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"], .tenMinutes)
XCTAssertEqual(change.newValue["1"], .halfHour)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = .halfHour
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,306 @@
import Foundation
import Defaults
import XCTest
import AppKit
private let fixtureColor = NSColor(red: CGFloat(103) / CGFloat(0xFF), green: CGFloat(132) / CGFloat(0xFF), blue: CGFloat(255) / CGFloat(0xFF), alpha: 1)
private let fixtureColor1 = NSColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(241) / CGFloat(0xFF), blue: CGFloat(180) / CGFloat(0xFF), alpha: 1)
private let fixtureColor2 = NSColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(180) / CGFloat(0xFF), blue: CGFloat(194) / CGFloat(0xFF), alpha: 1)
extension Defaults.Keys {
fileprivate static let color = Defaults.Key<NSColor>("NSColor", default: fixtureColor)
fileprivate static let colorArray = Defaults.Key<[NSColor]>("NSColorArray", default: [fixtureColor])
fileprivate static let colorDictionary = Defaults.Key<[String: NSColor]>("NSColorArray", default: ["0": fixtureColor])
}
final class DefaultsNSColorTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<NSColor>("independentNSColorKey", default: fixtureColor)
XCTAssertTrue(Defaults[key].isEqual(fixtureColor))
Defaults[key] = fixtureColor1
XCTAssertTrue(Defaults[key].isEqual(fixtureColor1))
}
func testOptionalKey() {
let key = Defaults.Key<NSColor?>("independentNSColorOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = fixtureColor
XCTAssertTrue(Defaults[key]?.isEqual(fixtureColor) ?? false)
}
func testArrayKey() {
let key = Defaults.Key<[NSColor]>("independentNSColorArrayKey", default: [fixtureColor])
XCTAssertTrue(Defaults[key][0].isEqual(fixtureColor))
Defaults[key].append(fixtureColor1)
XCTAssertTrue(Defaults[key][1].isEqual(fixtureColor1))
}
func testArrayOptionalKey() {
let key = Defaults.Key<[NSColor]?>("independentNSColorOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [fixtureColor]
Defaults[key]?.append(fixtureColor1)
XCTAssertTrue(Defaults[key]?[0].isEqual(fixtureColor) ?? false)
XCTAssertTrue(Defaults[key]?[1].isEqual(fixtureColor1) ?? false)
}
func testNestedArrayKey() {
let key = Defaults.Key<[[NSColor]]>("independentNSColorNestedArrayKey", default: [[fixtureColor]])
XCTAssertTrue(Defaults[key][0][0].isEqual(fixtureColor))
Defaults[key][0].append(fixtureColor1)
Defaults[key].append([fixtureColor2])
XCTAssertTrue(Defaults[key][0][1].isEqual(fixtureColor1))
XCTAssertTrue(Defaults[key][1][0].isEqual(fixtureColor2))
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: NSColor]]>("independentNSColorArrayDictionaryKey", default: [["0": fixtureColor]])
XCTAssertTrue(Defaults[key][0]["0"]?.isEqual(fixtureColor) ?? false)
Defaults[key][0]["1"] = fixtureColor1
Defaults[key].append(["0": fixtureColor2])
XCTAssertTrue(Defaults[key][0]["1"]?.isEqual(fixtureColor1) ?? false)
XCTAssertTrue(Defaults[key][1]["0"]?.isEqual(fixtureColor2) ?? false)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: NSColor]>("independentNSColorDictionaryKey", default: ["0": fixtureColor])
XCTAssertTrue(Defaults[key]["0"]?.isEqual(fixtureColor) ?? false)
Defaults[key]["1"] = fixtureColor1
XCTAssertTrue(Defaults[key]["1"]?.isEqual(fixtureColor1) ?? false)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: NSColor]?>("independentNSColorDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": fixtureColor]
Defaults[key]?["1"] = fixtureColor1
XCTAssertTrue(Defaults[key]?["0"]?.isEqual(fixtureColor) ?? false)
XCTAssertTrue(Defaults[key]?["1"]?.isEqual(fixtureColor1) ?? false)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [NSColor]]>("independentNSColorDictionaryArrayKey", default: ["0": [fixtureColor]])
XCTAssertTrue(Defaults[key]["0"]?[0].isEqual(fixtureColor) ?? false)
Defaults[key]["0"]?.append(fixtureColor1)
Defaults[key]["1"] = [fixtureColor2]
XCTAssertTrue(Defaults[key]["0"]?[1].isEqual(fixtureColor1) ?? false)
XCTAssertTrue(Defaults[key]["1"]?[0].isEqual(fixtureColor2) ?? false)
}
func testType() {
XCTAssert(Defaults[.color].isEqual(fixtureColor))
Defaults[.color] = fixtureColor1
XCTAssert(Defaults[.color].isEqual(fixtureColor1))
}
func testArrayType() {
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor))
Defaults[.colorArray][0] = fixtureColor1
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor1))
}
func testDictionaryType() {
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor) ?? false)
Defaults[.colorDictionary]["0"] = fixtureColor1
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor1) ?? false)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<NSColor>("observeNSColorKeyCombine", default: fixtureColor)
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
XCTAssertTrue(expected.0.isEqual(tuples[index].0))
XCTAssertTrue(expected.1.isEqual(tuples[index].1))
}
expect.fulfill()
}
Defaults[key] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<NSColor?>("observeNSColorOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(NSColor?, NSColor?)] = [(nil, fixtureColor), (fixtureColor, fixtureColor1), (fixtureColor1, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
guard let oldValue = expected.0 else {
XCTAssertNil(tuples[index].0)
continue
}
guard let newValue = expected.1 else {
XCTAssertNil(tuples[index].1)
continue
}
XCTAssertTrue(oldValue.isEqual(tuples[index].0))
XCTAssertTrue(newValue.isEqual(tuples[index].1))
}
expect.fulfill()
}
Defaults[key] = fixtureColor
Defaults[key] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[NSColor]>("observeNSColorArrayKeyCombine", default: [fixtureColor])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
XCTAssertTrue(expected.0.isEqual(tuples[index].0[0]))
XCTAssertTrue(expected.1.isEqual(tuples[index].1[0]))
}
expect.fulfill()
}
Defaults[key][0] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: NSColor]>("observeNSColorDictionaryKeyCombine", default: ["0": fixtureColor])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
XCTAssertTrue(expected.0.isEqual(tuples[index].0["0"]))
XCTAssertTrue(expected.1.isEqual(tuples[index].1["0"]))
}
expect.fulfill()
}
Defaults[key]["0"] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<NSColor>("observeNSColorKey", default: fixtureColor)
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue.isEqual(fixtureColor))
XCTAssertTrue(change.newValue.isEqual(fixtureColor1))
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureColor1
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<NSColor?>("observeNSColorOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertTrue(change.newValue?.isEqual(fixtureColor) ?? false)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureColor
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[NSColor]>("observeNSColorArrayKey", default: [fixtureColor])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue[0].isEqual(fixtureColor))
XCTAssertTrue(change.newValue[0].isEqual(fixtureColor))
XCTAssertTrue(change.newValue[1].isEqual(fixtureColor1))
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(fixtureColor1)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: NSColor]>("observeNSColorDictionaryKey", default: ["0": fixtureColor])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue["0"]?.isEqual(fixtureColor) ?? false)
XCTAssertTrue(change.newValue["0"]?.isEqual(fixtureColor) ?? false)
XCTAssertTrue(change.newValue["1"]?.isEqual(fixtureColor1) ?? false)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = fixtureColor1
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,474 @@
import Foundation
import CoreData
import Defaults
import XCTest
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
private final class ExamplePersistentHistory: NSPersistentHistoryToken, Defaults.Serializable {
let value: String
init(value: String) {
self.value = value
super.init()
}
required init?(coder: NSCoder) {
self.value = coder.decodeObject(forKey: "value") as! String
super.init()
}
override func encode(with coder: NSCoder) {
coder.encode(value, forKey: "value")
}
override class var supportsSecureCoding: Bool { true }
}
// NSSecureCoding
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
private let persistentHistoryValue = ExamplePersistentHistory(value: "ExampleToken")
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
extension Defaults.Keys {
fileprivate static let persistentHistory = Key<ExamplePersistentHistory>("persistentHistory", default: persistentHistoryValue)
fileprivate static let persistentHistoryArray = Key<[ExamplePersistentHistory]>("array_persistentHistory", default: [persistentHistoryValue])
fileprivate static let persistentHistoryDictionary = Key<[String: ExamplePersistentHistory]>("dictionary_persistentHistory", default: ["0": persistentHistoryValue])
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
final class DefaultsNSSecureCodingTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<ExamplePersistentHistory>("independentNSSecureCodingKey", default: persistentHistoryValue)
XCTAssertEqual(Defaults[key].value, persistentHistoryValue.value)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
Defaults[key] = newPersistentHistory
XCTAssertEqual(Defaults[key].value, newPersistentHistory.value)
}
func testOptionalKey() {
let key = Defaults.Key<ExamplePersistentHistory?>("independentNSSecureCodingOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = persistentHistoryValue
XCTAssertEqual(Defaults[key]?.value, persistentHistoryValue.value)
Defaults[key] = nil
XCTAssertNil(Defaults[key])
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
Defaults[key] = newPersistentHistory
XCTAssertEqual(Defaults[key]?.value, newPersistentHistory.value)
}
func testArrayKey() {
let key = Defaults.Key<[ExamplePersistentHistory]>("independentNSSecureCodingArrayKey", default: [persistentHistoryValue])
XCTAssertEqual(Defaults[key][0].value, persistentHistoryValue.value)
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
Defaults[key].append(newPersistentHistory1)
XCTAssertEqual(Defaults[key][1].value, newPersistentHistory1.value)
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
Defaults[key][1] = newPersistentHistory2
XCTAssertEqual(Defaults[key][1].value, newPersistentHistory2.value)
XCTAssertEqual(Defaults[key][0].value, persistentHistoryValue.value)
}
func testArrayOptionalKey() {
let key = Defaults.Key<[ExamplePersistentHistory]?>("independentNSSecureCodingArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [persistentHistoryValue]
XCTAssertEqual(Defaults[key]?[0].value, persistentHistoryValue.value)
Defaults[key] = nil
XCTAssertNil(Defaults[key])
}
func testNestedArrayKey() {
let key = Defaults.Key<[[ExamplePersistentHistory]]>("independentNSSecureCodingNestedArrayKey", default: [[persistentHistoryValue]])
XCTAssertEqual(Defaults[key][0][0].value, persistentHistoryValue.value)
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
Defaults[key][0].append(newPersistentHistory1)
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
Defaults[key].append([newPersistentHistory2])
XCTAssertEqual(Defaults[key][0][1].value, newPersistentHistory1.value)
XCTAssertEqual(Defaults[key][1][0].value, newPersistentHistory2.value)
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: ExamplePersistentHistory]]>("independentNSSecureCodingArrayDictionaryKey", default: [["0": persistentHistoryValue]])
XCTAssertEqual(Defaults[key][0]["0"]?.value, persistentHistoryValue.value)
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
Defaults[key][0]["1"] = newPersistentHistory1
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
Defaults[key].append(["0": newPersistentHistory2])
XCTAssertEqual(Defaults[key][0]["1"]?.value, newPersistentHistory1.value)
XCTAssertEqual(Defaults[key][1]["0"]?.value, newPersistentHistory2.value)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: ExamplePersistentHistory]>("independentNSSecureCodingDictionaryKey", default: ["0": persistentHistoryValue])
XCTAssertEqual(Defaults[key]["0"]?.value, persistentHistoryValue.value)
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
Defaults[key]["1"] = newPersistentHistory1
XCTAssertEqual(Defaults[key]["1"]?.value, newPersistentHistory1.value)
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
Defaults[key]["1"] = newPersistentHistory2
XCTAssertEqual(Defaults[key]["1"]?.value, newPersistentHistory2.value)
XCTAssertEqual(Defaults[key]["0"]?.value, persistentHistoryValue.value)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: ExamplePersistentHistory]?>("independentNSSecureCodingDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": persistentHistoryValue]
XCTAssertEqual(Defaults[key]?["0"]?.value, persistentHistoryValue.value)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [ExamplePersistentHistory]]>("independentNSSecureCodingDictionaryArrayKey", default: ["0": [persistentHistoryValue]])
XCTAssertEqual(Defaults[key]["0"]?[0].value, persistentHistoryValue.value)
let newPersistentHistory1 = ExamplePersistentHistory(value: "NewValue1")
Defaults[key]["0"]?.append(newPersistentHistory1)
let newPersistentHistory2 = ExamplePersistentHistory(value: "NewValue2")
Defaults[key]["1"] = [newPersistentHistory2]
XCTAssertEqual(Defaults[key]["0"]?[1].value, newPersistentHistory1.value)
XCTAssertEqual(Defaults[key]["1"]?[0].value, newPersistentHistory2.value)
}
func testType() {
XCTAssertEqual(Defaults[.persistentHistory].value, persistentHistoryValue.value)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
Defaults[.persistentHistory] = newPersistentHistory
XCTAssertEqual(Defaults[.persistentHistory].value, newPersistentHistory.value)
}
func testArrayType() {
XCTAssertEqual(Defaults[.persistentHistoryArray][0].value, persistentHistoryValue.value)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
Defaults[.persistentHistoryArray][0] = newPersistentHistory
XCTAssertEqual(Defaults[.persistentHistoryArray][0].value, newPersistentHistory.value)
}
func testDictionaryType() {
XCTAssertEqual(Defaults[.persistentHistoryDictionary]["0"]?.value, persistentHistoryValue.value)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
Defaults[.persistentHistoryDictionary]["0"] = newPersistentHistory
XCTAssertEqual(Defaults[.persistentHistoryDictionary]["0"]?.value, newPersistentHistory.value)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKeyCombine", default: persistentHistoryValue)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue.value, $0.newValue.value) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(persistentHistoryValue.value, newPersistentHistory.value), (newPersistentHistory.value, persistentHistoryValue.value)].enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = newPersistentHistory
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKeyCombine")
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue?.value, $0.newValue?.value) }
.collect(3)
let expectedValue: [(ExamplePersistentHistory?, ExamplePersistentHistory?)] = [(nil, persistentHistoryValue), (persistentHistoryValue, newPersistentHistory), (newPersistentHistory, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0?.value, tuples[index].0)
XCTAssertEqual(expected.1?.value, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = persistentHistoryValue
Defaults[key] = newPersistentHistory
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[ExamplePersistentHistory]>("observeNSSecureCodingArrayKeyCombine", default: [persistentHistoryValue])
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(ExamplePersistentHistory, ExamplePersistentHistory)] = [(persistentHistoryValue, newPersistentHistory), (newPersistentHistory, persistentHistoryValue)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0.value, tuples[index].0[0].value)
XCTAssertEqual(expected.1.value, tuples[index].1[0].value)
}
expect.fulfill()
}
Defaults[key][0] = newPersistentHistory
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: ExamplePersistentHistory]>("observeNSSecureCodingDictionaryKeyCombine", default: ["0": persistentHistoryValue])
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(ExamplePersistentHistory, ExamplePersistentHistory)] = [(persistentHistoryValue, newPersistentHistory), (newPersistentHistory, persistentHistoryValue)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0.value, tuples[index].0["0"]?.value)
XCTAssertEqual(expected.1.value, tuples[index].1["0"]?.value)
}
expect.fulfill()
}
Defaults[key]["0"] = newPersistentHistory
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveMultipleNSSecureKeysCombine() {
let key1 = Defaults.Key<ExamplePersistentHistory>("observeMultipleNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
let key2 = Defaults.Key<ExamplePersistentHistory>("observeMultipleNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
let cancellable = publisher.sink { _ in
expect.fulfill()
}
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveMultipleNSSecureOptionalKeysCombine() {
let key1 = Defaults.Key<ExamplePersistentHistory?>("observeMultipleNSSecureCodingOptionalKey1")
let key2 = Defaults.Key<ExamplePersistentHistory?>("observeMultipleNSSecureCodingOptionalKeyKey2")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
let cancellable = publisher.sink { _ in
expect.fulfill()
}
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
func testObserveMultipleNSSecureKeys() {
let key1 = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
let key2 = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
var counter = 0
observation = Defaults.observe(keys: key1, key2, options: []) {
counter += 1
if counter == 2 {
expect.fulfill()
} else if counter > 2 {
XCTFail()
}
}
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
observation.invalidate()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testRemoveDuplicatesObserveNSSecureCodingKeyCombine() {
let key = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3"]
let cancellable = Defaults
.publisher(key, options: [])
.removeDuplicates()
.map(\.newValue.value)
.collect(expectedArray.count)
.sink { result in
print("Result array: \(result)")
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
}
inputArray.forEach {
Defaults[key] = ExamplePersistentHistory(value: $0)
}
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testRemoveDuplicatesObserveNSSecureCodingOptionalKeyCombine() {
let key = Defaults.Key<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKey")
let expect = expectation(description: "Observation closure being called")
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3", nil]
let cancellable = Defaults
.publisher(key, options: [])
.removeDuplicates()
.map(\.newValue)
.map { $0?.value }
.collect(expectedArray.count)
.sink { result in
print("Result array: \(result)")
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
}
inputArray.forEach {
Defaults[key] = ExamplePersistentHistory(value: $0)
}
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<ExamplePersistentHistory>("observeNSSecureCodingKey", default: persistentHistoryValue)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue.value, persistentHistoryValue.value)
XCTAssertEqual(change.newValue.value, newPersistentHistory.value)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = newPersistentHistory
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<ExamplePersistentHistory?>("observeNSSecureCodingOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue?.value, persistentHistoryValue.value)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = persistentHistoryValue
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[ExamplePersistentHistory]>("observeNSSecureCodingArrayKey", default: [persistentHistoryValue])
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0].value, persistentHistoryValue.value)
XCTAssertEqual(change.newValue.map { $0.value }, [persistentHistoryValue, newPersistentHistory].map { $0.value })
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(newPersistentHistory)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: ExamplePersistentHistory]>("observeNSSecureCodingDictionaryKey", default: ["0": persistentHistoryValue])
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"]?.value, persistentHistoryValue.value)
XCTAssertEqual(change.newValue["0"]?.value, persistentHistoryValue.value)
XCTAssertEqual(change.newValue["1"]?.value, newPersistentHistory.value)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = newPersistentHistory
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,361 @@
import Foundation
import XCTest
import Defaults
private struct Item: Equatable, Hashable {
let name: String
let count: UInt
}
extension Item: Defaults.Serializable {
static let bridge = ItemBridge()
}
private struct ItemBridge: Defaults.Bridge {
typealias Value = Item
typealias Serializable = [String: String]
func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return ["name": value.name, "count": String(value.count)]
}
func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let name = object["name"],
let count = UInt(object["count"] ?? "0")
else {
return nil
}
return Value(name: name, count: count)
}
}
private let fixtureSetAlgebra = Item(name: "Apple", count: 10)
private let fixtureSetAlgebra1 = Item(name: "Banana", count: 20)
private let fixtureSetAlgebra2 = Item(name: "Grape", count: 30)
private let fixtureSetAlgebra3 = Item(name: "Guava", count: 40)
extension Defaults.Keys {
fileprivate static let setAlgebraCustomElement = Key<DefaultsSetAlgebra<Item>>("setAlgebraCustomElement", default: .init([fixtureSetAlgebra]))
fileprivate static let setAlgebraCustomElementArray = Key<[DefaultsSetAlgebra<Item>]>("setAlgebraArrayCustomElement", default: [.init([fixtureSetAlgebra])])
fileprivate static let setAlgebraCustomElementDictionary = Key<[String: DefaultsSetAlgebra<Item>]>("setAlgebraDictionaryCustomElement", default: ["0": .init([fixtureSetAlgebra])])
}
final class DefaultsSetAlgebraCustomElementTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Item>>("independentSetAlgebraKey", default: .init([fixtureSetAlgebra]))
Defaults[key].insert(fixtureSetAlgebra)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
Defaults[key].insert(fixtureSetAlgebra1)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
}
func testOptionalKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Item>?>("independentSetAlgebraOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = .init([fixtureSetAlgebra])
Defaults[key]?.insert(fixtureSetAlgebra)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
Defaults[key]?.insert(fixtureSetAlgebra1)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
}
func testArrayKey() {
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]>("independentSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
Defaults[key][0].insert(fixtureSetAlgebra1)
Defaults[key].append(.init([fixtureSetAlgebra2]))
Defaults[key][1].insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testArrayOptionalKey() {
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]?>("independentSetAlgebraArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [.init([fixtureSetAlgebra])]
Defaults[key]?[0].insert(fixtureSetAlgebra1)
Defaults[key]?.append(.init([fixtureSetAlgebra2]))
Defaults[key]?[1].insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]?[1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testNestedArrayKey() {
let key = Defaults.Key<[[DefaultsSetAlgebra<Item>]]>("independentSetAlgebraNestedArrayKey", default: [[.init([fixtureSetAlgebra])]])
Defaults[key][0][0].insert(fixtureSetAlgebra1)
Defaults[key][0].append(.init([fixtureSetAlgebra1]))
Defaults[key][0][1].insert(fixtureSetAlgebra2)
Defaults[key].append([.init([fixtureSetAlgebra3])])
Defaults[key][1][0].insert(fixtureSetAlgebra2)
XCTAssertEqual(Defaults[key][0][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key][0][1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
XCTAssertEqual(Defaults[key][1][0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: DefaultsSetAlgebra<Item>]]>("independentSetAlgebraArrayDictionaryKey", default: [["0": .init([fixtureSetAlgebra])]])
Defaults[key][0]["0"]?.insert(fixtureSetAlgebra1)
Defaults[key][0]["1"] = .init([fixtureSetAlgebra1])
Defaults[key][0]["1"]?.insert(fixtureSetAlgebra2)
Defaults[key].append(["0": .init([fixtureSetAlgebra3])])
Defaults[key][1]["0"]?.insert(fixtureSetAlgebra2)
XCTAssertEqual(Defaults[key][0]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key][0]["1"], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
XCTAssertEqual(Defaults[key][1]["0"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryKey() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]>("independentSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
Defaults[key]["1"] = .init([fixtureSetAlgebra2])
Defaults[key]["1"]?.insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]?>("independentSetAlgebraDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": .init([fixtureSetAlgebra])]
Defaults[key]?["0"]?.insert(fixtureSetAlgebra1)
Defaults[key]?["1"] = .init([fixtureSetAlgebra2])
Defaults[key]?["1"]?.insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key]?["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]?["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [DefaultsSetAlgebra<Item>]]>("independentSetAlgebraDictionaryArrayKey", default: ["0": [.init([fixtureSetAlgebra])]])
Defaults[key]["0"]?[0].insert(fixtureSetAlgebra1)
Defaults[key]["0"]?.append(.init([fixtureSetAlgebra1]))
Defaults[key]["0"]?[1].insert(fixtureSetAlgebra2)
Defaults[key]["1"] = [.init([fixtureSetAlgebra3])]
Defaults[key]["1"]?[0].insert(fixtureSetAlgebra2)
XCTAssertEqual(Defaults[key]["0"]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]["0"]?[1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
XCTAssertEqual(Defaults[key]["1"]?[0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testType() {
let (inserted, _) = Defaults[.setAlgebraCustomElement].insert(fixtureSetAlgebra)
XCTAssertFalse(inserted)
Defaults[.setAlgebraCustomElement].insert(fixtureSetAlgebra1)
XCTAssertEqual(Defaults[.setAlgebraCustomElement], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
}
func testArrayType() {
Defaults[.setAlgebraCustomElementArray][0].insert(fixtureSetAlgebra1)
Defaults[.setAlgebraCustomElementArray].append(.init([fixtureSetAlgebra2]))
Defaults[.setAlgebraCustomElementArray][1].insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[.setAlgebraCustomElementArray][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[.setAlgebraCustomElementArray][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryType() {
Defaults[.setAlgebraCustomElementDictionary]["0"]?.insert(fixtureSetAlgebra1)
Defaults[.setAlgebraCustomElementDictionary]["1"] = .init([fixtureSetAlgebra2])
Defaults[.setAlgebraCustomElementDictionary]["1"]?.insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[.setAlgebraCustomElementDictionary]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[.setAlgebraCustomElementDictionary]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<DefaultsSetAlgebra<Item>>("observeSetAlgebraKeyCombine", default: .init([fixtureSetAlgebra]))
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(DefaultsSetAlgebra<Item>, DefaultsSetAlgebra<Item>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key].insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<DefaultsSetAlgebra<Item>?>("observeSetAlgebraOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(DefaultsSetAlgebra<Item>?, DefaultsSetAlgebra<Item>?)] = [(nil, .init([fixtureSetAlgebra])), (.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = .init([fixtureSetAlgebra])
Defaults[key]?.insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]>("observeSetAlgebraArrayKeyCombine", default: [.init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(DefaultsSetAlgebra<Item>, DefaultsSetAlgebra<Item>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key][0].insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]>("observeSetAlgebraDictionaryKeyCombine", default: ["0": .init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(DefaultsSetAlgebra<Item>, DefaultsSetAlgebra<Item>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"])
XCTAssertEqual(expected.1, tuples[index].1["0"])
}
expect.fulfill()
}
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Item>>("observeSetAlgebraKey", default: .init([fixtureSetAlgebra]))
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, .init([fixtureSetAlgebra]))
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
observation.invalidate()
expect.fulfill()
}
Defaults[key].insert(fixtureSetAlgebra1)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Item>?>("observeSetAlgebraOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra]))
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .init([fixtureSetAlgebra])
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[DefaultsSetAlgebra<Item>]>("observeSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], .init([fixtureSetAlgebra]))
XCTAssertEqual(change.newValue[1], .init([fixtureSetAlgebra]))
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(.init([fixtureSetAlgebra]))
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictioanryKey() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Item>]>("observeSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"], .init([fixtureSetAlgebra]))
XCTAssertEqual(change.newValue["1"], .init([fixtureSetAlgebra]))
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = .init([fixtureSetAlgebra])
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,396 @@
import Foundation
import XCTest
import Defaults
struct DefaultsSetAlgebra<Element: Defaults.Serializable & Hashable>: SetAlgebra {
var store = Set<Element>()
init() {}
init<S: Sequence>(_ sequence: __owned S) where Element == S.Element {
self.store = Set(sequence)
}
init(_ store: Set<Element>) {
self.store = store
}
func contains(_ member: Element) -> Bool {
store.contains(member)
}
func union(_ other: DefaultsSetAlgebra) -> DefaultsSetAlgebra {
DefaultsSetAlgebra(store.union(other.store))
}
func intersection(_ other: DefaultsSetAlgebra)
-> DefaultsSetAlgebra {
var defaultsSetAlgebra = DefaultsSetAlgebra()
defaultsSetAlgebra.store = store.intersection(other.store)
return defaultsSetAlgebra
}
func symmetricDifference(_ other: DefaultsSetAlgebra)
-> DefaultsSetAlgebra {
var defaultedSetAlgebra = DefaultsSetAlgebra()
defaultedSetAlgebra.store = store.symmetricDifference(other.store)
return defaultedSetAlgebra
}
@discardableResult
mutating func insert(_ newMember: Element)
-> (inserted: Bool, memberAfterInsert: Element) {
store.insert(newMember)
}
mutating func remove(_ member: Element) -> Element? {
store.remove(member)
}
mutating func update(with newMember: Element) -> Element? {
store.update(with: newMember)
}
mutating func formUnion(_ other: DefaultsSetAlgebra) {
store.formUnion(other.store)
}
mutating func formSymmetricDifference(_ other: DefaultsSetAlgebra) {
store.formSymmetricDifference(other.store)
}
mutating func formIntersection(_ other: DefaultsSetAlgebra) {
store.formIntersection(other.store)
}
}
extension DefaultsSetAlgebra: Defaults.SetAlgebraSerializable {
func toArray() -> [Element] {
Array(store)
}
}
private let fixtureSetAlgebra = 0
private let fixtureSetAlgebra1 = 1
private let fixtureSetAlgebra2 = 2
private let fixtureSetAlgebra3 = 3
extension Defaults.Keys {
fileprivate static let setAlgebra = Key<DefaultsSetAlgebra<Int>>("setAlgebra", default: .init([fixtureSetAlgebra]))
fileprivate static let setAlgebraArray = Key<[DefaultsSetAlgebra<Int>]>("setAlgebraArray", default: [.init([fixtureSetAlgebra])])
fileprivate static let setAlgebraDictionary = Key<[String: DefaultsSetAlgebra<Int>]>("setAlgebraDictionary", default: ["0": .init([fixtureSetAlgebra])])
}
final class DefaultsSetAlgebraTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Int>>("independentSetAlgebraKey", default: .init([fixtureSetAlgebra]))
Defaults[key].insert(fixtureSetAlgebra)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
Defaults[key].insert(fixtureSetAlgebra1)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
}
func testOptionalKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Int>?>("independentSetAlgebraOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = .init([fixtureSetAlgebra])
Defaults[key]?.insert(fixtureSetAlgebra)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra]))
Defaults[key]?.insert(fixtureSetAlgebra1)
XCTAssertEqual(Defaults[key], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
}
func testArrayKey() {
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]>("independentSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
Defaults[key][0].insert(fixtureSetAlgebra1)
Defaults[key].append(.init([fixtureSetAlgebra2]))
Defaults[key][1].insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testArrayOptionalKey() {
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]?>("independentSetAlgebraArrayOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [.init([fixtureSetAlgebra])]
Defaults[key]?[0].insert(fixtureSetAlgebra1)
Defaults[key]?.append(.init([fixtureSetAlgebra2]))
Defaults[key]?[1].insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]?[1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testNestedArrayKey() {
let key = Defaults.Key<[[DefaultsSetAlgebra<Int>]]>("independentSetAlgebraNestedArrayKey", default: [[.init([fixtureSetAlgebra])]])
Defaults[key][0][0].insert(fixtureSetAlgebra1)
Defaults[key][0].append(.init([fixtureSetAlgebra1]))
Defaults[key][0][1].insert(fixtureSetAlgebra2)
Defaults[key].append([.init([fixtureSetAlgebra3])])
Defaults[key][1][0].insert(fixtureSetAlgebra2)
XCTAssertEqual(Defaults[key][0][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key][0][1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
XCTAssertEqual(Defaults[key][1][0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: DefaultsSetAlgebra<Int>]]>("independentSetAlgebraArrayDictionaryKey", default: [["0": .init([fixtureSetAlgebra])]])
Defaults[key][0]["0"]?.insert(fixtureSetAlgebra1)
Defaults[key][0]["1"] = .init([fixtureSetAlgebra1])
Defaults[key][0]["1"]?.insert(fixtureSetAlgebra2)
Defaults[key].append(["0": .init([fixtureSetAlgebra3])])
Defaults[key][1]["0"]?.insert(fixtureSetAlgebra2)
XCTAssertEqual(Defaults[key][0]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key][0]["1"], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
XCTAssertEqual(Defaults[key][1]["0"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryKey() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]>("independentSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
Defaults[key]["1"] = .init([fixtureSetAlgebra2])
Defaults[key]["1"]?.insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]?>("independentSetAlgebraDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": .init([fixtureSetAlgebra])]
Defaults[key]?["0"]?.insert(fixtureSetAlgebra1)
Defaults[key]?["1"] = .init([fixtureSetAlgebra2])
Defaults[key]?["1"]?.insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[key]?["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]?["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [DefaultsSetAlgebra<Int>]]>("independentSetAlgebraDictionaryArrayKey", default: ["0": [.init([fixtureSetAlgebra])]])
Defaults[key]["0"]?[0].insert(fixtureSetAlgebra1)
Defaults[key]["0"]?.append(.init([fixtureSetAlgebra1]))
Defaults[key]["0"]?[1].insert(fixtureSetAlgebra2)
Defaults[key]["1"] = [.init([fixtureSetAlgebra3])]
Defaults[key]["1"]?[0].insert(fixtureSetAlgebra2)
XCTAssertEqual(Defaults[key]["0"]?[0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[key]["0"]?[1], .init([fixtureSetAlgebra1, fixtureSetAlgebra2]))
XCTAssertEqual(Defaults[key]["1"]?[0], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testType() {
let (inserted, _) = Defaults[.setAlgebra].insert(fixtureSetAlgebra)
XCTAssertFalse(inserted)
Defaults[.setAlgebra].insert(fixtureSetAlgebra1)
XCTAssertEqual(Defaults[.setAlgebra], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
}
func testArrayType() {
Defaults[.setAlgebraArray][0].insert(fixtureSetAlgebra1)
Defaults[.setAlgebraArray].append(.init([fixtureSetAlgebra2]))
Defaults[.setAlgebraArray][1].insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[.setAlgebraArray][0], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[.setAlgebraArray][1], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
func testDictionaryType() {
Defaults[.setAlgebraDictionary]["0"]?.insert(fixtureSetAlgebra1)
Defaults[.setAlgebraDictionary]["1"] = .init([fixtureSetAlgebra2])
Defaults[.setAlgebraDictionary]["1"]?.insert(fixtureSetAlgebra3)
XCTAssertEqual(Defaults[.setAlgebraDictionary]["0"], .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
XCTAssertEqual(Defaults[.setAlgebraDictionary]["1"], .init([fixtureSetAlgebra2, fixtureSetAlgebra3]))
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<DefaultsSetAlgebra<Int>>("observeSetAlgebraKeyCombine", default: .init([fixtureSetAlgebra]))
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(DefaultsSetAlgebra<Int>, DefaultsSetAlgebra<Int>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key].insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<DefaultsSetAlgebra<Int>?>("observeSetAlgebraOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(DefaultsSetAlgebra<Int>?, DefaultsSetAlgebra<Int>?)] = [(nil, .init([fixtureSetAlgebra])), (.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0)
XCTAssertEqual(expected.1, tuples[index].1)
}
expect.fulfill()
}
Defaults[key] = .init([fixtureSetAlgebra])
Defaults[key]?.insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]>("observeSetAlgebraArrayKeyCombine", default: [.init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(DefaultsSetAlgebra<Int>, DefaultsSetAlgebra<Int>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0[0])
XCTAssertEqual(expected.1, tuples[index].1[0])
}
expect.fulfill()
}
Defaults[key][0].insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]>("observeSetAlgebraDictionaryKeyCombine", default: ["0": .init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let expectedValue: [(DefaultsSetAlgebra<Int>, DefaultsSetAlgebra<Int>)] = [(.init([fixtureSetAlgebra]), .init([fixtureSetAlgebra, fixtureSetAlgebra1])), (.init([fixtureSetAlgebra, fixtureSetAlgebra1]), .init([fixtureSetAlgebra]))]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
XCTAssertEqual(expected.0, tuples[index].0["0"])
XCTAssertEqual(expected.1, tuples[index].1["0"])
}
expect.fulfill()
}
Defaults[key]["0"]?.insert(fixtureSetAlgebra1)
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Int>>("observeSetAlgebraKey", default: .init([fixtureSetAlgebra]))
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, .init([fixtureSetAlgebra]))
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra, fixtureSetAlgebra1]))
observation.invalidate()
expect.fulfill()
}
Defaults[key].insert(fixtureSetAlgebra1)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<DefaultsSetAlgebra<Int>?>("observeSetAlgebraOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue, .init([fixtureSetAlgebra]))
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .init([fixtureSetAlgebra])
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[DefaultsSetAlgebra<Int>]>("observeSetAlgebraArrayKey", default: [.init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue[0], .init([fixtureSetAlgebra]))
XCTAssertEqual(change.newValue[1], .init([fixtureSetAlgebra]))
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(.init([fixtureSetAlgebra]))
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictioanryKey() {
let key = Defaults.Key<[String: DefaultsSetAlgebra<Int>]>("observeSetAlgebraDictionaryKey", default: ["0": .init([fixtureSetAlgebra])])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue["0"], .init([fixtureSetAlgebra]))
XCTAssertEqual(change.newValue["1"], .init([fixtureSetAlgebra]))
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = .init([fixtureSetAlgebra])
observation.invalidate()
waitForExpectations(timeout: 10)
}
}

View File

@ -0,0 +1,55 @@
import Foundation
import Defaults
import XCTest
private let fixtureSet = Set(1...5)
extension Defaults.Keys {
fileprivate static let set = Key<Set<Int>>("setInt", default: fixtureSet)
}
final class DefaultsSetTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<Set<Int>>("independentSetKey", default: fixtureSet)
XCTAssertEqual(Defaults[key].count, fixtureSet.count)
Defaults[key].insert(6)
XCTAssertEqual(Defaults[key], Set(1...6))
}
func testOptionalKey() {
let key = Defaults.Key<Set<Int>?>("independentSetOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = fixtureSet
XCTAssertEqual(Defaults[key]?.count, fixtureSet.count)
Defaults[key]?.insert(6)
XCTAssertEqual(Defaults[key], Set(1...6))
}
func testArrayKey() {
let key = Defaults.Key<[Set<Int>]>("independentSetArrayKey", default: [fixtureSet])
XCTAssertEqual(Defaults[key][0].count, fixtureSet.count)
Defaults[key][0].insert(6)
XCTAssertEqual(Defaults[key][0], Set(1...6))
Defaults[key].append(Set(1...4))
XCTAssertEqual(Defaults[key][1], Set(1...4))
}
func testDictionaryKey() {
let key = Defaults.Key<[String: Set<Int>]>("independentSetArrayKey", default: ["0": fixtureSet])
XCTAssertEqual(Defaults[key]["0"]?.count, fixtureSet.count)
Defaults[key]["0"]?.insert(6)
XCTAssertEqual(Defaults[key]["0"], Set(1...6))
Defaults[key]["1"] = Set(1...4)
XCTAssertEqual(Defaults[key]["1"], Set(1...4))
}
}

View File

@ -0,0 +1,48 @@
import XCTest
import Foundation
import SwiftUI
import Defaults
extension Defaults.Keys {
fileprivate static let hasUnicorn = Key<Bool>("swiftui_hasUnicorn", default: false)
fileprivate static let user = Key<User>("swiftui_user", default: User(username: "Hank", password: "123456"))
fileprivate static let setInt = Key<Set<Int>>("swiftui_setInt", default: Set(1...3))
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
struct ContentView: View {
@Default(.hasUnicorn) var hasUnicorn
@Default(.user) var user
@Default(.setInt) var setInt
var body: some View {
Text("User \(user.username) has Unicorn: \(String(hasUnicorn))")
Toggle("Toggle Unicorn", isOn: $hasUnicorn)
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
final class DefaultsSwiftUITests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testSwiftUIObserve() {
let view = ContentView()
XCTAssertFalse(view.hasUnicorn)
XCTAssertEqual(view.user.username, "Hank")
XCTAssertEqual(view.setInt.count, 3)
view.user = User(username: "Chen", password: "123456")
view.hasUnicorn.toggle()
view.setInt.insert(4)
XCTAssertTrue(view.hasUnicorn)
XCTAssertEqual(view.user.username, "Chen")
XCTAssertEqual(view.setInt, Set(1...4))
}
}

View File

@ -1,53 +1,20 @@
import Foundation
import CoreData
import Combine
import XCTest
import Defaults
let fixtureURL = URL(string: "https://sindresorhus.com")!
let fixtureFileURL = URL(string: "file://~/icon.png")!
let fixtureURL2 = URL(string: "https://example.com")!
enum FixtureEnum: String, Codable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
let fixtureDate = Date()
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
final class ExamplePersistentHistory: NSPersistentHistoryToken {
let value: String
init(value: String) {
self.value = value
super.init()
}
required init?(coder: NSCoder) {
self.value = coder.decodeObject(forKey: "value") as! String
super.init()
}
override func encode(with coder: NSCoder) {
coder.encode(value, forKey: "value")
}
override class var supportsSecureCoding: Bool { true }
}
extension Defaults.Keys {
static let key = Key<Bool>("key", default: false)
static let url = Key<URL>("url", default: fixtureURL)
static let `enum` = Key<FixtureEnum>("enum", default: .oneHour)
static let file = Key<URL>("fileURL", default: fixtureFileURL)
static let data = Key<Data>("data", default: Data([]))
static let date = Key<Date>("date", default: fixtureDate)
// NSSecureCoding
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
static let persistentHistoryValue = ExamplePersistentHistory(value: "ExampleToken")
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
static let persistentHistory = NSSecureCodingKey<ExamplePersistentHistory>("persistentHistory", default: persistentHistoryValue)
}
final class DefaultsTests: XCTestCase {
@ -70,13 +37,21 @@ final class DefaultsTests: XCTestCase {
func testOptionalKey() {
let key = Defaults.Key<Bool?>("independentOptionalKey")
let url = Defaults.Key<URL?>("independentOptionalURLKey")
XCTAssertNil(Defaults[key])
XCTAssertNil(Defaults[url])
Defaults[key] = true
Defaults[url] = fixtureURL
XCTAssertTrue(Defaults[key]!)
XCTAssertEqual(Defaults[url], fixtureURL)
Defaults[key] = nil
Defaults[url] = nil
XCTAssertNil(Defaults[key])
XCTAssertNil(Defaults[url])
Defaults[key] = false
Defaults[url] = fixtureURL2
XCTAssertFalse(Defaults[key]!)
XCTAssertEqual(Defaults[url], fixtureURL2)
}
func testKeyRegistersDefault() {
@ -104,29 +79,15 @@ final class DefaultsTests: XCTestCase {
XCTAssertTrue(Defaults[.key])
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
func testNSSecureCodingKeys() {
XCTAssertEqual(Defaults.Keys.persistentHistoryValue.value, Defaults[.persistentHistory].value)
let newPersistentHistory = ExamplePersistentHistory(value: "NewValue")
Defaults[.persistentHistory] = newPersistentHistory
XCTAssertEqual(newPersistentHistory.value, Defaults[.persistentHistory].value)
}
func testUrlType() {
XCTAssertEqual(Defaults[.url], fixtureURL)
let newUrl = URL(string: "https://twitter.com")!
Defaults[.url] = newUrl
XCTAssertEqual(Defaults[.url], newUrl)
}
func testEnumType() {
XCTAssertEqual(Defaults[.enum], FixtureEnum.oneHour)
}
func testDataType() {
XCTAssertEqual(Defaults[.data], Data([]))
let newData = Data([0xFF])
Defaults[.data] = newData
XCTAssertEqual(Defaults[.data], newData)
@ -134,12 +95,15 @@ final class DefaultsTests: XCTestCase {
func testDateType() {
XCTAssertEqual(Defaults[.date], fixtureDate)
let newDate = Date()
Defaults[.date] = newDate
XCTAssertEqual(Defaults[.date], newDate)
}
func testFileURLType() {
XCTAssertEqual(Defaults[.file], fixtureFileURL)
}
func testRemoveAll() {
let key = Defaults.Key<Bool>("removeAll", default: false)
let key2 = Defaults.Key<Bool>("removeAll2", default: false)
@ -189,39 +153,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveNSSecureCodingKeyCombine() {
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue.value, $0.newValue.value) }
.collect(3)
let expectedValues = [
("TestValue", "NewTestValue"),
("NewTestValue", "NewTestValue2"),
("NewTestValue2", "TestValue")
]
let cancellable = publisher.sink { actualValues in
for (expected, actual) in zip(expectedValues, actualValues) {
XCTAssertEqual(expected.0, actual.0)
XCTAssertEqual(expected.1, actual.1)
}
expect.fulfill()
}
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue2")
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<Bool?>("observeOptionalKey")
@ -251,40 +182,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveNSSecureCodingOptionalKeyCombine() {
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue?.value, $0.newValue?.value) }
.collect(3)
let expectedValues: [(String?, String?)] = [
(nil, "NewTestValue"),
("NewTestValue", "NewTestValue2"),
("NewTestValue2", nil)
]
let cancellable = publisher.sink { actualValues in
for (expected, actual) in zip(expectedValues, actualValues) {
XCTAssertEqual(expected.0, actual.0)
XCTAssertEqual(expected.1, actual.1)
}
expect.fulfill()
}
XCTAssertNil(Defaults[key])
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue2")
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveMultipleKeysCombine() {
let key1 = Defaults.Key<String>("observeKey1", default: "x")
@ -304,26 +201,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveMultipleNSSecureKeysCombine() {
let key1 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
let key2 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
let cancellable = publisher.sink { _ in
expect.fulfill()
}
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveMultipleOptionalKeysCombine() {
let key1 = Defaults.Key<String?>("observeOptionalKey1")
@ -343,25 +220,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveMultipleNSSecureOptionalKeysCombine() {
let key1 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingKey1")
let key2 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingKey2")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults.publisher(keys: key1, key2, options: []).collect(2)
let cancellable = publisher.sink { _ in
expect.fulfill()
}
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testReceiveValueBeforeSubscriptionCombine() {
let key = Defaults.Key<String>("receiveValueBeforeSubscription", default: "hello")
@ -400,24 +258,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
func testObserveNSSecureCodingKey() {
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue.value, "TestValue")
XCTAssertEqual(change.newValue.value, "NewTestValue")
observation.invalidate()
expect.fulfill()
}
Defaults[key] = ExamplePersistentHistory(value: "NewTestValue")
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<Bool?>("observeOptionalKey")
let expect = expectation(description: "Observation closure being called")
@ -435,24 +275,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
func testObserveNSSecureCodingOptionalKey() {
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertEqual(change.newValue?.value, "NewOptionalValue")
observation.invalidate()
expect.fulfill()
}
Defaults[key] = ExamplePersistentHistory(value: "NewOptionalValue")
waitForExpectations(timeout: 10)
}
func testObserveMultipleKeys() {
let key1 = Defaults.Key<String>("observeKey1", default: "x")
let key2 = Defaults.Key<Bool>("observeKey2", default: true)
@ -476,33 +298,7 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *)
func testObserveMultipleNSSecureKeys() {
let key1 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey1", default: ExamplePersistentHistory(value: "TestValue"))
let key2 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey2", default: ExamplePersistentHistory(value: "TestValue"))
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
var counter = 0
observation = Defaults.observe(keys: key1, key2, options: []) {
counter += 1
if counter == 2 {
expect.fulfill()
} else if counter > 2 {
XCTFail()
}
}
Defaults[key1] = ExamplePersistentHistory(value: "NewTestValue1")
Defaults[key2] = ExamplePersistentHistory(value: "NewTestValue2")
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveKeyURL() {
let fixtureURL = URL(string: "https://sindresorhus.com")!
let fixtureURL2 = URL(string: "https://example.com")!
let key = Defaults.Key<URL>("observeKeyURL", default: fixtureURL)
let expect = expectation(description: "Observation closure being called")
@ -519,23 +315,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
func testObserveKeyEnum() {
let key = Defaults.Key<FixtureEnum>("observeKeyEnum", default: .oneHour)
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertEqual(change.oldValue, .oneHour)
XCTAssertEqual(change.newValue, .tenMinutes)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = .tenMinutes
waitForExpectations(timeout: 10)
}
func testObservePreventPropagation() {
let key1 = Defaults.Key<Bool?>("preventPropagation0", default: nil)
let expect = expectation(description: "No infinite recursion")
@ -594,7 +373,7 @@ final class DefaultsTests: XCTestCase {
XCTAssert(Defaults[key1]! == 4)
expect.fulfill()
} else {
usleep(100_000)
usleep(300_000)
print("--- Release: \(Thread.isMainThread)")
}
}
@ -699,120 +478,6 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
func testResetKey() {
let defaultFixture1 = "foo1"
let defaultFixture2 = 0
let defaultFixture3 = "foo3"
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String>("key1", default: defaultFixture1)
let key2 = Defaults.Key<Int>("key2", default: defaultFixture2)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults.reset(key1)
XCTAssertEqual(Defaults[key1], defaultFixture1)
XCTAssertEqual(Defaults[key2], newFixture2)
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
let key3 = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("key3", default: ExamplePersistentHistory(value: defaultFixture3))
Defaults[key3] = ExamplePersistentHistory(value: newFixture3)
Defaults.reset(key3)
XCTAssertEqual(Defaults[key3].value, defaultFixture3)
}
}
func testResetMultipleKeys() {
let defaultFxiture1 = "foo1"
let defaultFixture2 = 0
let defaultFixture3 = "foo3"
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1)
let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2)
let key3 = Defaults.Key<String>("akey3", default: defaultFixture3)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults[key3] = newFixture3
Defaults.reset(key1, key2)
XCTAssertEqual(Defaults[key1], defaultFxiture1)
XCTAssertEqual(Defaults[key2], defaultFixture2)
XCTAssertEqual(Defaults[key3], newFixture3)
}
func testResetOptionalKey() {
let newString1 = "bar1"
let newString2 = "bar2"
let newString3 = "bar3"
let key1 = Defaults.Key<String?>("optionalKey1")
let key2 = Defaults.Key<String?>("optionalKey2")
Defaults[key1] = newString1
Defaults[key2] = newString2
Defaults.reset(key1)
XCTAssertNil(Defaults[key1])
XCTAssertEqual(Defaults[key2], newString2)
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, iOSApplicationExtension 11.0, macOSApplicationExtension 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
let key3 = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("optionalKey3")
Defaults[key3] = ExamplePersistentHistory(value: newString3)
Defaults.reset(key3)
XCTAssertNil(Defaults[key3])
}
}
func testResetMultipleOptionalKeys() {
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String?>("aoptionalKey1")
let key2 = Defaults.Key<Int?>("aoptionalKey2")
let key3 = Defaults.Key<String?>("aoptionalKey3")
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults[key3] = newFixture3
Defaults.reset(key1, key2)
XCTAssertNil(Defaults[key1])
XCTAssertNil(Defaults[key2])
XCTAssertEqual(Defaults[key3], newFixture3)
}
func testObserveWithLifetimeTie() {
let key = Defaults.Key<Bool>("lifetimeTie", default: false)
let expect = expectation(description: "Observation closure being called")
weak var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { _ in
observation.invalidate()
expect.fulfill()
}
.tieToLifetime(of: self)
Defaults[key] = true
waitForExpectations(timeout: 10)
}
func testObserveWithLifetimeTieManualBreak() {
let key = Defaults.Key<Bool>("lifetimeTieManualBreak", default: false)
weak var observation: Defaults.Observation? = Defaults.observe(key, options: []) { _ in }.tieToLifetime(of: self)
observation!.removeLifetimeTie()
for index in 1...10 {
if observation == nil {
break
}
sleep(1)
if index == 10 {
XCTFail()
}
}
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testRemoveDuplicatesObserveKeyCombine() {
let key = Defaults.Key<Bool>("observeKey", default: false)
@ -869,61 +534,88 @@ final class DefaultsTests: XCTestCase {
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testRemoveDuplicatesObserveNSSecureCodingKeyCombine() {
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
func testResetKey() {
let defaultFixture1 = "foo1"
let defaultFixture2 = 0
let newFixture1 = "bar1"
let newFixture2 = 1
let key1 = Defaults.Key<String>("key1", default: defaultFixture1)
let key2 = Defaults.Key<Int>("key2", default: defaultFixture2)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults.reset(key1)
XCTAssertEqual(Defaults[key1], defaultFixture1)
XCTAssertEqual(Defaults[key2], newFixture2)
}
func testResetMultipleKeys() {
let defaultFxiture1 = "foo1"
let defaultFixture2 = 0
let defaultFixture3 = "foo3"
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1)
let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2)
let key3 = Defaults.Key<String>("akey3", default: defaultFixture3)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults[key3] = newFixture3
Defaults.reset(key1, key2)
XCTAssertEqual(Defaults[key1], defaultFxiture1)
XCTAssertEqual(Defaults[key2], defaultFixture2)
XCTAssertEqual(Defaults[key3], newFixture3)
}
func testResetMultipleOptionalKeys() {
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String?>("aoptionalKey1")
let key2 = Defaults.Key<Int?>("aoptionalKey2")
let key3 = Defaults.Key<String?>("aoptionalKey3")
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults[key3] = newFixture3
Defaults.reset(key1, key2)
XCTAssertNil(Defaults[key1])
XCTAssertNil(Defaults[key2])
XCTAssertEqual(Defaults[key3], newFixture3)
}
func testObserveWithLifetimeTie() {
let key = Defaults.Key<Bool>("lifetimeTie", default: false)
let expect = expectation(description: "Observation closure being called")
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3"]
let cancellable = Defaults
.publisher(key, options: [])
.removeDuplicates()
.map(\.newValue.value)
.collect(expectedArray.count)
.sink { result in
print("Result array: \(result)")
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
}
inputArray.forEach {
Defaults[key] = ExamplePersistentHistory(value: $0)
weak var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { _ in
observation.invalidate()
expect.fulfill()
}
.tieToLifetime(of: self)
Defaults.reset(key)
cancellable.cancel()
Defaults[key] = true
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testRemoveDuplicatesObserveNSSecureCodingOptionalKeyCombine() {
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
let expect = expectation(description: "Observation closure being called")
func testObserveWithLifetimeTieManualBreak() {
let key = Defaults.Key<Bool>("lifetimeTieManualBreak", default: false)
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3", nil]
weak var observation: Defaults.Observation? = Defaults.observe(key, options: []) { _ in }.tieToLifetime(of: self)
observation!.removeLifetimeTie()
let cancellable = Defaults
.publisher(key, options: [])
.removeDuplicates()
.map(\.newValue)
.map { $0?.value }
.collect(expectedArray.count)
.sink { result in
print("Result array: \(result)")
result == expectedArray ? expect.fulfill() : XCTFail("Expected Array is not matched")
for index in 1...10 {
if observation == nil {
break
}
inputArray.forEach {
Defaults[key] = ExamplePersistentHistory(value: $0)
sleep(1)
if index == 10 {
XCTFail()
}
}
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)

View File

@ -0,0 +1,308 @@
#if !os(macOS)
import Foundation
import Defaults
import XCTest
import UIKit
private let fixtureColor = UIColor(red: CGFloat(103) / CGFloat(0xFF), green: CGFloat(132) / CGFloat(0xFF), blue: CGFloat(255) / CGFloat(0xFF), alpha: 1)
private let fixtureColor1 = UIColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(241) / CGFloat(0xFF), blue: CGFloat(180) / CGFloat(0xFF), alpha: 1)
private let fixtureColor2 = UIColor(red: CGFloat(255) / CGFloat(0xFF), green: CGFloat(180) / CGFloat(0xFF), blue: CGFloat(194) / CGFloat(0xFF), alpha: 1)
extension Defaults.Keys {
fileprivate static let color = Defaults.Key<UIColor>("NSColor", default: fixtureColor)
fileprivate static let colorArray = Defaults.Key<[UIColor]>("NSColorArray", default: [fixtureColor])
fileprivate static let colorDictionary = Defaults.Key<[String: UIColor]>("NSColorArray", default: ["0": fixtureColor])
}
final class DefaultsNSColorTests: XCTestCase {
override func setUp() {
super.setUp()
Defaults.removeAll()
}
override func tearDown() {
super.tearDown()
Defaults.removeAll()
}
func testKey() {
let key = Defaults.Key<UIColor>("independentNSColorKey", default: fixtureColor)
XCTAssertTrue(Defaults[key].isEqual(fixtureColor))
Defaults[key] = fixtureColor1
XCTAssertTrue(Defaults[key].isEqual(fixtureColor1))
}
func testOptionalKey() {
let key = Defaults.Key<UIColor?>("independentNSColorOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = fixtureColor
XCTAssertTrue(Defaults[key]?.isEqual(fixtureColor) ?? false)
}
func testArrayKey() {
let key = Defaults.Key<[UIColor]>("independentNSColorArrayKey", default: [fixtureColor])
XCTAssertTrue(Defaults[key][0].isEqual(fixtureColor))
Defaults[key].append(fixtureColor1)
XCTAssertTrue(Defaults[key][1].isEqual(fixtureColor1))
}
func testArrayOptionalKey() {
let key = Defaults.Key<[UIColor]?>("independentNSColorOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = [fixtureColor]
Defaults[key]?.append(fixtureColor1)
XCTAssertTrue(Defaults[key]?[0].isEqual(fixtureColor) ?? false)
XCTAssertTrue(Defaults[key]?[1].isEqual(fixtureColor1) ?? false)
}
func testNestedArrayKey() {
let key = Defaults.Key<[[UIColor]]>("independentNSColorNestedArrayKey", default: [[fixtureColor]])
XCTAssertTrue(Defaults[key][0][0].isEqual(fixtureColor))
Defaults[key][0].append(fixtureColor1)
Defaults[key].append([fixtureColor2])
XCTAssertTrue(Defaults[key][0][1].isEqual(fixtureColor1))
XCTAssertTrue(Defaults[key][1][0].isEqual(fixtureColor2))
}
func testArrayDictionaryKey() {
let key = Defaults.Key<[[String: UIColor]]>("independentNSColorArrayDictionaryKey", default: [["0": fixtureColor]])
XCTAssertTrue(Defaults[key][0]["0"]?.isEqual(fixtureColor) ?? false)
Defaults[key][0]["1"] = fixtureColor1
Defaults[key].append(["0": fixtureColor2])
XCTAssertTrue(Defaults[key][0]["1"]?.isEqual(fixtureColor1) ?? false)
XCTAssertTrue(Defaults[key][1]["0"]?.isEqual(fixtureColor2) ?? false)
}
func testDictionaryKey() {
let key = Defaults.Key<[String: UIColor]>("independentNSColorDictionaryKey", default: ["0": fixtureColor])
XCTAssertTrue(Defaults[key]["0"]?.isEqual(fixtureColor) ?? false)
Defaults[key]["1"] = fixtureColor1
XCTAssertTrue(Defaults[key]["1"]?.isEqual(fixtureColor1) ?? false)
}
func testDictionaryOptionalKey() {
let key = Defaults.Key<[String: UIColor]?>("independentNSColorDictionaryOptionalKey")
XCTAssertNil(Defaults[key])
Defaults[key] = ["0": fixtureColor]
Defaults[key]?["1"] = fixtureColor1
XCTAssertTrue(Defaults[key]?["0"]?.isEqual(fixtureColor) ?? false)
XCTAssertTrue(Defaults[key]?["1"]?.isEqual(fixtureColor1) ?? false)
}
func testDictionaryArrayKey() {
let key = Defaults.Key<[String: [UIColor]]>("independentNSColorDictionaryArrayKey", default: ["0": [fixtureColor]])
XCTAssertTrue(Defaults[key]["0"]?[0].isEqual(fixtureColor) ?? false)
Defaults[key]["0"]?.append(fixtureColor1)
Defaults[key]["1"] = [fixtureColor2]
XCTAssertTrue(Defaults[key]["0"]?[1].isEqual(fixtureColor1) ?? false)
XCTAssertTrue(Defaults[key]["1"]?[0].isEqual(fixtureColor2) ?? false)
}
func testType() {
XCTAssert(Defaults[.color].isEqual(fixtureColor))
Defaults[.color] = fixtureColor1
XCTAssert(Defaults[.color].isEqual(fixtureColor1))
}
func testArrayType() {
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor))
Defaults[.colorArray][0] = fixtureColor1
XCTAssertTrue(Defaults[.colorArray][0].isEqual(fixtureColor1))
}
func testDictionaryType() {
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor) ?? false)
Defaults[.colorDictionary]["0"] = fixtureColor1
XCTAssertTrue(Defaults[.colorDictionary]["0"]?.isEqual(fixtureColor1) ?? false)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveKeyCombine() {
let key = Defaults.Key<UIColor>("observeNSColorKeyCombine", default: fixtureColor)
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
XCTAssertTrue(expected.0.isEqual(tuples[index].0))
XCTAssertTrue(expected.1.isEqual(tuples[index].1))
}
expect.fulfill()
}
Defaults[key] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveOptionalKeyCombine() {
let key = Defaults.Key<UIColor?>("observeNSColorOptionalKeyCombine")
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(3)
let expectedValue: [(UIColor?, UIColor?)] = [(nil, fixtureColor), (fixtureColor, fixtureColor1), (fixtureColor1, nil)]
let cancellable = publisher.sink { tuples in
for (index, expected) in expectedValue.enumerated() {
guard let oldValue = expected.0 else {
XCTAssertNil(tuples[index].0)
continue
}
guard let newValue = expected.1 else {
XCTAssertNil(tuples[index].1)
continue
}
XCTAssertTrue(oldValue.isEqual(tuples[index].0))
XCTAssertTrue(newValue.isEqual(tuples[index].1))
}
expect.fulfill()
}
Defaults[key] = fixtureColor
Defaults[key] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveArrayKeyCombine() {
let key = Defaults.Key<[UIColor]>("observeNSColorArrayKeyCombine", default: [fixtureColor])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
XCTAssertTrue(expected.0.isEqual(tuples[index].0[0]))
XCTAssertTrue(expected.1.isEqual(tuples[index].1[0]))
}
expect.fulfill()
}
Defaults[key][0] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, iOSApplicationExtension 13.0, macOSApplicationExtension 10.15, tvOSApplicationExtension 13.0, watchOSApplicationExtension 6.0, *)
func testObserveDictionaryKeyCombine() {
let key = Defaults.Key<[String: UIColor]>("observeNSColorDictionaryKeyCombine", default: ["0": fixtureColor])
let expect = expectation(description: "Observation closure being called")
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.collect(2)
let cancellable = publisher.sink { tuples in
for (index, expected) in [(fixtureColor, fixtureColor1), (fixtureColor1, fixtureColor)].enumerated() {
XCTAssertTrue(expected.0.isEqual(tuples[index].0["0"]))
XCTAssertTrue(expected.1.isEqual(tuples[index].1["0"]))
}
expect.fulfill()
}
Defaults[key]["0"] = fixtureColor1
Defaults.reset(key)
cancellable.cancel()
waitForExpectations(timeout: 10)
}
func testObserveKey() {
let key = Defaults.Key<UIColor>("observeNSColorKey", default: fixtureColor)
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue.isEqual(fixtureColor))
XCTAssertTrue(change.newValue.isEqual(fixtureColor1))
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureColor1
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveOptionalKey() {
let key = Defaults.Key<UIColor?>("observeNSColorOptionalKey")
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertNil(change.oldValue)
XCTAssertTrue(change.newValue?.isEqual(fixtureColor) ?? false)
observation.invalidate()
expect.fulfill()
}
Defaults[key] = fixtureColor
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveArrayKey() {
let key = Defaults.Key<[UIColor]>("observeNSColorArrayKey", default: [fixtureColor])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue[0].isEqual(fixtureColor))
XCTAssertTrue(change.newValue[0].isEqual(fixtureColor))
XCTAssertTrue(change.newValue[1].isEqual(fixtureColor1))
observation.invalidate()
expect.fulfill()
}
Defaults[key].append(fixtureColor1)
observation.invalidate()
waitForExpectations(timeout: 10)
}
func testObserveDictionaryKey() {
let key = Defaults.Key<[String: UIColor]>("observeNSColorDictionaryKey", default: ["0": fixtureColor])
let expect = expectation(description: "Observation closure being called")
var observation: Defaults.Observation!
observation = Defaults.observe(key, options: []) { change in
XCTAssertTrue(change.oldValue["0"]?.isEqual(fixtureColor) ?? false)
XCTAssertTrue(change.newValue["0"]?.isEqual(fixtureColor) ?? false)
XCTAssertTrue(change.newValue["1"]?.isEqual(fixtureColor1) ?? false)
observation.invalidate()
expect.fulfill()
}
Defaults[key]["1"] = fixtureColor1
observation.invalidate()
waitForExpectations(timeout: 10)
}
}
#endif

404
migration.md Normal file
View File

@ -0,0 +1,404 @@
# Migration Guide From v4 to v5
## Warning
If the migration is not success or incomplete. Edit `Defaults.Key` might cause data loss.
**Please back up your UserDefaults data before migration.**
## Summary
Before v4, `Defaults` store `Codable` types as a JSON string.
After v5, `Defaults` store `Defaults.Serializable` types with `UserDefaults` native supported type.
```swift
// Before
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
UserDefaults.standard.string(forKey: "key") //=> "["0": 0]"
// After v5
let key = Defaults.Key<[String: Int]>("key", default: ["0": 0])
UserDefaults.standard.dictionary(forKey: "key") //=> [0: 0]
```
All types should conform to `Defaults.Serializable` in order to work with `Defaults`.
So this will require some migrations to resolve **TWO** major issues.
## Issues
1. **Compiler complain that `Defaults.Key<Value>` is not conform to `Defaults.Serializable`.**
Since we replace `Codable` with `Defaults.Serializable`, `Key<Value>` will have to conform to `Value: Defaults.Serializable`.
For this situation, please follow the guide below:
- [From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5](#from-codable-struct-in-defaults-v4-to-codable-struct-in-defaults-v5)
- [From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5](#from-codable-enum-in-defaults-v4-to-codable-enum-in-defaults-v5)
2. **Previous value in UserDefaults is not readable. (ex. `Defaults[.array]` return `null`).**
In v5, `Defaults` reads value from `UserDefaults` as a native supported type.
But `UserDefaults` only contains JSON string before migration, `Defaults` will not be able to work with it.
For this situation, `Defaults` provides `Defaults.migrate` method to automate the migration process.
- [From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Native Supported Elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-native-supported-elements-in-defaults-v5)
- [From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Codable Elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionarysetwith-codable-elements-in-defaults-v5)
**Caution:**
- This is a breaking change, there is no way to convert it back to `Codable Array/Dictionary/Set` so far.
- **Optional migration**
`Defaults` also provide a migration guide to let users convert them `Codable` things into the UserDefaults native supported type, but it is optional.
- [From `Codable` enum in Defaults v4 to `RawRepresentable` in Defaults v5](#from-codable-enum-in-defaults-v4-to-rawrepresentable-in-defaults-v5-optional)
- [From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5](#from-codable-struct-in-defaults-v4-to-dictionary-in-defaults-v5-optional)
## Testing
We recommend user doing some tests after migration.
The most critical issue is the second one (Previous value in UserDefaults is not readable).
After migration, there is a need to make sure user can get the same value as before.
You can try to test it manually or making a test file to test it.
Here is the guide for making a migration test:
For example you are trying to migrate a `Codable String` array to native array.
1. Get previous value in UserDefaults (using `defaults` command or whatever you want).
```swift
let string = "[\"a\",\"b\",\"c\"]"
```
2. Insert the value above into UserDefaults.
```swift
UserDefaults.standard.set(string, forKey: "testKey")
```
3. Call `Defaults.migrate` and then using `Defaults` to get its value
```swift
let key = Defaults.Key<[String]>("testKey", default: [])
Defaults.migrate(key, to: .v5)
Defaults[key] //=> [a, b, c]
```
---
### From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5
Before v4, `struct` have to conform to `Codable` to store it as a JSON string.
After v5, `struct` have to conform to `Defaults.Serializable & Codable` to store it as a JSON string.
#### Before migration, your code should be like this
```swift
private struct TimeZone: Codable {
var id: String
var name: String
}
extension Defaults.Keys {
static let timezone = Defaults.Key<TimeZone?>("TimeZone")
}
```
#### Migration steps
1. Let `TimeZone` conform to `Defaults.Serializable`.
```swift
private struct TimeZone: Defaults.Serializable, Codable {
var id: String
var name: String
}
```
2. Now `Defaults[.timezone]` should be readable.
---
### From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5
Before v4, `enum` have to conform to `Codable` to store it as a JSON string.
After v5, struct have to conform to `Defaults.Serializable & Codable` to store it as a JSON string.
#### Before migration, your code should be like this
```swift
private enum Period: String, Codable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension Defaults.Keys {
static let period = Defaults.Key<Period?>("period")
}
```
#### Migration steps
1. Let `Period` conform to `Defaults.Serializable`.
```swift
private enum Period: String, Defaults.Serializable, Codable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
```
2. Now `Defaults[.period]` should be readable.
---
### From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Native Supported Elements) in Defaults v5
Before v4, `Defaults` will store array/dictionary as a JSON string(`["a", "b", "c"]`).
After v5, `Defaults` will store it as a native array/dictionary with native supported elements(`[a, b, c]` ).
#### Before migration, your code should be like this
```swift
extension Defaults.Keys {
static let arrayString = Defaults.Key<[String]?>("arrayString")
static let setString = Defaults.Key<Set<String>?>("setString")
static let dictionaryStringInt = Defaults.Key<[String: Int]?>("dictionaryStringInt")
static let dictionaryStringIntInArray = Defaults.Key<[[String: Int]]?>("dictionaryStringIntInArray")
}
```
#### Migration steps
1. **Call `Defaults.migration(.arrayString, to: .v5)`, `Defaults.migration(.setString, to: .v5)`, `Defaults.migration(.dictionaryStringInt, to: .v5)`, `Defaults.migration(.dictionaryStringIntInArray, to: .v5)`.**
2. Now `Defaults[.arrayString]`, `Defaults.[.setString]`, `Defaults[.dictionaryStringInt]`, `Defaults[.dictionaryStringIntInArray]` should be readable.
---
### From `Codable Array/Dictionary/Set` in Defaults v4 to `Native Array/Dictionary/Set`(With Codable Elements) in Defaults v5
Before v4, `Defaults` will store array/dictionary as a JSON string(`"{ "id": "0", "name": "Asia/Taipei" }"`, `"["10 Minutes", "30 Minutes"]"`).
After v5, `Defaults` will store it as a native array/dictionary with codable elements(`{ id: 0, name: Asia/Taipei }`, `[10 Minutes, 30 Minutes]`).
#### Before migration, your code should be like this
```swift
private struct TimeZone: Codable, Hashable {
var id: String
var name: String
}
private enum Period: String, Codable, Hashable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension Defaults.Keys {
static let arrayTimezone = Defaults.Key<[TimeZone]?>("arrayTimezone")
static let setTimezone = Defaults.Key<[TimeZone]?>("setTimezone")
static let arrayPeriod = Defaults.Key<[Period]?>("arrayPeriod")
static let setPeriod = Defaults.Key<[Period]?>("setPeriod")
static let dictionaryTimezone = Defaults.Key<[String: TimeZone]?>("dictionaryTimezone")
static let dictionaryPeriod = Defaults.Key<[String: Period]?>("dictionaryPeriod")
}
```
#### Migration steps
1. Let `TimeZone` and `Period` conform to `Defaults.Serializable`
```swift
private struct TimeZone: Defaults.Serializable, Codable, Hashable {
var id: String
var name: String
}
private enum Period: String, Defaults.Serializable, Codable, Hashable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
```
2. **Call `Defaults.migration(.arrayTimezone, to: .v5)`, `Defaults.migration(.setTimezone, to: .v5)`, `Defaults.migration(.dictionaryTimezone, to: .v5)`, `Defaults.migration(.arrayPeriod, to: .v5)`, `Defaults.migration(.setPeriod, to: .v5)` , `Defaults.migration(.dictionaryPeriod, to: .v5)`.**
3. Now `Defaults[.arrayTimezone]`, `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]`, `Defaults[.arrayPeriod]`, `Defaults[.setPeriod]` , `Defaults[.dictionaryPeriod]` should be readable.
---
### From `Codable` enum in Defaults v4 to `RawRepresentable` in Defaults v5 (Optional)
Before v4, `Defaults` will store `enum` as a JSON string(`"10 Minutes"`).
After v5, `Defaults` will store `enum` as a `RawRepresentable`(`10 Minutes`).
#### Before migration, your code should be like this
```swift
private enum Period: String, Codable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension Defaults.Keys {
static let period = Defaults.Key<Period?>("period")
}
```
#### Migration steps
1. Create an enum call `CodablePeriod` and create an extension of it. Let it conform to `Defaults.CodableType` and associated `NativeForm` to `Period`.
```swift
private enum CodablePeriod: String {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
extension CodablePeriod: Defaults.CodableType {
typealias NativeForm = Period
}
```
2. Remove `Codable`. So `Period` can be stored natively.
```swift
private enum Period: String {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
}
```
3. Create an extension of `Period`, let it conform to `Defaults.NativeType` and its `CodableForm` should be `CodablePeriod`.
```swift
extension Period: Defaults.NativeType {
typealias CodableForm = CodablePeriod
}
```
4. **Call `Defaults.migration(.period)`**
5. Now `Defaults[.period]` should be readable.
* hints: You can also implement `toNative` function at `Defaults.CodableType` in your own way.
For example
```swift
extension CodablePeriod: Defaults.CodableType {
typealias NativeForm = Period
public func toNative() -> Period {
switch self {
case .tenMinutes:
return .tenMinutes
case .halfHour:
return .halfHour
case .oneHour:
return .oneHour
}
}
}
```
---
### From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5 (Optional)
This happens when you have a struct which is stored as a codable JSON string before, but now you want it to be stored as a native UserDefaults dictionary.
#### Before migration, your code should be like this
```swift
private struct TimeZone: Codable {
var id: String
var name: String
}
extension Defaults.Keys {
static let timezone = Defaults.Key<TimeZone?>("TimeZone")
static let arrayTimezone = Defaults.Key<[TimeZone]?>("arrayTimezone")
static let setTimezone = Defaults.Key<Set<TimeZone>?>("setTimezone")
static let dictionaryTimezone = Defaults.Key<[String: TimeZone]?>("setTimezone")
}
```
#### Migration steps
1. Create a `TimeZoneBridge` which conform to `Defaults.Bridge` and its `Value` is TimeZone, `Serializable` is `[String: String]`.
```swift
private struct TimeZoneBridge: Defaults.Bridge {
typealias Value = TimeZone
typealias Serializable = [String: String]
func serialize(_ value: TimeZone?) -> Serializable? {
guard let value = value else {
return nil
}
return ["id": value.id, "name": value.name]
}
func deserialize(_ object: Serializable?) -> TimeZone? {
guard
let dictionary = object,
let id = dictionary["id"],
let name = dictionary["name"]
else {
return nil
}
return TimeZone(id: id, name: name)
}
}
```
2. Create an extension of `TimeZone`, let it conform to `Defaults.NativeType` and its static bridge is `TimeZoneBridge`(Compiler will complain that `TimeZone` is not conform to `Defaults.NativeType`, will resolve it later).
```swift
private struct TimeZone: Hashable {
var id: String
var name: String
}
extension TimeZone: Defaults.NativeType {
static let bridge = TimeZoneBridge()
}
```
3. Create an extension of `CodableTimeZone` and let it conform to `Defaults.CodableType`
```swift
private struct CodableTimeZone {
var id: String
var name: String
}
extension CodableTimeZone: Defaults.CodableType {
/// Convert from `Codable` to `Native`
func toNative() -> TimeZone {
TimeZone(id: id, name: name)
}
}
```
4. Associate `TimeZone.CodableForm` to `CodableTimeZone`
```swift
extension TimeZone: Defaults.NativeType {
/// Associated `CodableForm` to `CodableTimeZone`
typealias CodableForm = CodableTimeZone
static let bridge = TimeZoneBridge()
}
```
5. **Call `Defaults.migration(.timezone, to: .v5)`, `Defaults.migration(.arrayTimezone, to: .v5)`, `Defaults.migration(.setTimezone, to: .v5)`, `Defaults.migration(.dictionaryTimezone, to: .v5)`**.
6. Now `Defaults[.timezone]`, `Defaults[.arrayTimezone]` , `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]` should be readable.
**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.**

404
readme.md
View File

@ -19,6 +19,7 @@ For a real-world example, see my [Plash app](https://github.com/sindresorhus/Pla
- **Publishers:** Combine publishers built-in.
- **Observation:** Observe changes to keys.
- **Debuggable:** The data is stored as JSON-serialized values.
- **Customizable:** You can serialize and deserialize your own type in your own way.
## Compatibility
@ -27,6 +28,10 @@ For a real-world example, see my [Plash app](https://github.com/sindresorhus/Pla
- tvOS 10+
- watchOS 3+
## Migration Guides
#### [From v4 to v5](./migration.md)
## Install
#### Swift Package Manager
@ -45,6 +50,29 @@ github "sindresorhus/Defaults"
pod 'Defaults'
```
## Support types
| Single Value |
|:------------------:|
| `Int(8/16/32/64)` |
| `UInt(8/16/32/64)` |
| `Double` |
| `Float` |
| `String` |
| `CGFloat` |
| `Bool` |
| `Date` |
| `Data` |
| `URL` |
| `NSColor` (macOS) |
| `UIColor` (iOS) |
| `Codable` |
The list above only show the type that does not need further more configuration.
We also support them wrapped in `Array`, `Set`, `Dictionary` even wrapped in nested type. ex. `[[String: Set<[String: Int]>]]`.
For more types, see [Enum Example](#enum-example), [Codable Example](#codable-example) or [Advanced Usage](#advanced-usage).
For more examples, see [Tests/DefaultsTests](./Tests/DefaultsTests).
## Usage
You declare the defaults keys upfront with type and default value.
@ -92,30 +120,10 @@ The default value is then `nil`.
---
If you have `NSSecureCoding` classes which you want to save, you can use them as follows:
```swift
extension Defaults.Keys {
static let someSecureCoding = NSSecureCodingKey<SomeNSSecureCodingClass>("someSecureCoding", default: SomeNSSecureCodingClass(string: "Default", int: 5, bool: true))
static let someOptionalSecureCoding = NSSecureCodingOptionalKey<Double>("someOptionalSecureCoding")
}
Defaults[.someSecureCoding].string
//=> "Default"
Defaults[.someSecureCoding].int
//=> 5
Defaults[.someSecureCoding].bool
//=> true
```
You can use those keys just like in all the other examples. The return value will be your `NSSecureCoding` class.
### Enum example
```swift
enum DurationKeys: String, Codable {
enum DurationKeys: String, Defaults.Serializable {
case tenMinutes = "10 Minutes"
case halfHour = "30 Minutes"
case oneHour = "1 Hour"
@ -129,6 +137,22 @@ Defaults[.defaultDuration].rawValue
//=> "1 Hour"
```
### Codable Example
```swift
struct User: Codable, Defaults.Serializable {
let name: String
let age: String
}
extension Defaults.Keys {
static let user = Key<User>("user", default: .init(name: "Hello", age: "24"))
}
Defaults[.user].name
//=> "Hello"
```
### Use keys directly
You are not required to attach keys to `Defaults.Keys`.
@ -163,8 +187,6 @@ Note that it's `@Default`, not `@Defaults`.
You cannot use `@Default` in an `ObservableObject`. It's meant to be used in a `View`.
This is only implemented for `Defaults.Key`. PR welcome for `Defaults.NSSecureCoding` if you need it.
### Observe changes to a key
```swift
@ -317,6 +339,217 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
//=> true
```
## Advanced Usage
### Serialization of custom types
Although `Defaults` already support many types internal, there might have some situations where you want to use your own type.
The guide below will show you how to make your own custom type works with `Defaults`.
1. Create your own custom type.
```swift
struct User {
let name: String
let age: String
}
```
2. Create a bridge which protocol conforms to `Defaults.Bridge`.
```swift
struct UserBridge: Defaults.Bridge {
typealias Value = User
typealias Serializable = [String: String]
public func serialize(_ value: Value?) -> Serializable? {
guard let value = value else {
return nil
}
return ["name": value.name, "age": value.age]
}
public func deserialize(_ object: Serializable?) -> Value? {
guard
let object = object,
let name = object["name"],
let age = object["age"]
else {
return nil
}
return User(name: name, age: age)
}
}
```
3. Create an extension of `User`, let its protocol conforms to `Defaults.Serializable` and its static bridge should be the bridge we created above.
```swift
struct User {
let name: String
let age: String
}
extension User: Defaults.Serializable {
static let bridge = UserBridge()
}
```
4. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let user = Defaults.Key<User>("user", default: User(name: "Hello", age: "24"))
static let arrayUser = Defaults.Key<[User]>("arrayUser", default: [User(name: "Hello", age: "24")])
static let setUser = Defaults.Key<Set<User>>("user", default: Set([User(name: "Hello", age: "24")]))
static let dictionaryUser = Defaults.Key<[String: User]>("dictionaryUser", default: ["user": User(name: "Hello", age: "24")])
}
Defaults[.user].name //=> "Hello"
Defaults[.arrayUser][0].name //=> "Hello"
Defaults[.setUser].first?.name //=> "Hello"
Defaults[.dictionaryUser]["user"]?.name //=> "Hello"
```
### Serialization of Collection
1. Create your Collection and its element should conforms to `Defaults.Serializable`.
```swift
struct Bag<Element: Defaults.Serializable>: Collection {
var items: [Element]
var startIndex: Int {
items.startIndex
}
var endIndex: Int {
items.endIndex
}
mutating func insert(element: Element, at: Int) {
items.insert(element, at: at)
}
func index(after index: Int) -> Int {
items.index(after: index)
}
subscript(position: Int) -> Element {
items[position]
}
}
```
2. Create an extension of `Bag`. let it conforms to `Defaults.CollectionSerializable`
```swift
extension Bag: Defaults.CollectionSerializable {
init(_ elements: [Element]) {
self.items = elements
}
}
```
3. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let stringBag = Key<Bag<String>>("stringBag", default: Bag(["Hello", "World!"]))
}
Defaults[.stringBag][0] //=> "Hello"
Defaults[.stringBag][1] //=> "World!"
```
### Serialization of SetAlgebra
1. Create your SetAlgebra and its element should conforms to `Defaults.Serializable & Hashable`
```swift
struct SetBag<Element: Defaults.Serializable & Hashable>: SetAlgebra {
var store = Set<Element>()
init() {}
init(_ store: Set<Element>) {
self.store = store
}
func contains(_ member: Element) -> Bool {
store.contains(member)
}
func union(_ other: SetBag) -> SetBag {
SetBag(store.union(other.store))
}
func intersection(_ other: SetBag)
-> SetBag {
var setBag = SetBag()
setBag.store = store.intersection(other.store)
return setBag
}
func symmetricDifference(_ other: SetBag)
-> SetBag {
var setBag = SetBag()
setBag.store = store.symmetricDifference(other.store)
return setBag
}
@discardableResult
mutating func insert(_ newMember: Element)
-> (inserted: Bool, memberAfterInsert: Element) {
store.insert(newMember)
}
mutating func remove(_ member: Element) -> Element? {
store.remove(member)
}
mutating func update(with newMember: Element) -> Element? {
store.update(with: newMember)
}
mutating func formUnion(_ other: SetBag) {
store.formUnion(other.store)
}
mutating func formSymmetricDifference(_ other: SetBag) {
store.formSymmetricDifference(other.store)
}
mutating func formIntersection(_ other: SetBag) {
store.formIntersection(other.store)
}
}
```
2. Create an extension of `SetBag`. Let it conforms to `Defaults.SetAlgebraSerializable`
```swift
extension SetBag: Defaults.SetAlgebraSerializable {
func toArray() -> [Element] {
Array(store)
}
}
```
3. Create some keys and enjoy it.
```swift
extension Defaults.Keys {
static let stringSet = Key<SetBag<String>>("stringSet", default: SetBag(["Hello", "World!"]))
}
Defaults[.stringSet].contains("Hello") //=> true
Defaults[.stringSet].contains("World!") //=> true
```
## API
### `Defaults`
@ -327,7 +560,7 @@ Type: `class`
Stores the keys.
#### `Defaults.Key` *(alias `Defaults.Keys.Key`)*
#### `Defaults.Key` _(alias `Defaults.Keys.Key`)_
```swift
Defaults.Key<T>(_ key: String, default: T, suite: UserDefaults = .standard)
@ -339,27 +572,48 @@ Create a key with a default value.
The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with a Interface Builder binding.
#### `Defaults.NSSecureCodingKey` *(alias `Defaults.Keys.NSSecureCodingKey`)*
#### `Defaults.Serializable`
```swift
Defaults.NSSecureCodingKey<T>(_ key: String, default: T, suite: UserDefaults = .standard)
public protocol DefaultsSerializable {
typealias Value = Bridge.Value
typealias Serializable = Bridge.Serializable
associatedtype Bridge: Defaults.Bridge
static var bridge: Bridge { get }
}
```
Type: `class`
Type: `protocol`
Create a NSSecureCoding key with a default value.
All types conform to this protocol will be able to work with `Defaults`.
The default value is written to the actual `UserDefaults` and can be used elsewhere. For example, with a Interface Builder binding.
It should have a static variable `bridge` which protocol should conform to `Defaults.Bridge`.
#### `Defaults.NSSecureCodingOptionalKey` *(alias `Defaults.Keys.NSSecureCodingOptionalKey`)*
#### `Defaults.Bridge`
```swift
Defaults.NSSecureCodingOptionalKey<T>(_ key: String, suite: UserDefaults = .standard)
public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize(_ value: Value?) -> Serializable?
func deserialize(_ object: Serializable?) -> Value?
}
```
Type: `class`
Type: `protocol`
Create a NSSecureCoding key with an optional value.
A Bridge can do serialization and de-serialization.
Have two associate types `Value` and `Serializable`.
`Value` is the type user want to use it.
`Serializable` is the type stored in `UserDefaults`.
`serialize` will be executed before storing to the `UserDefaults` .
`deserialize` will be executed after retrieving its value from the `UserDefaults`.
#### `Defaults.reset(keys…)`
@ -381,22 +635,6 @@ Defaults.observe<T: Codable>(
) -> Defaults.Observation
```
```swift
Defaults.observe<T: NSSecureCoding>(
_ key: Defaults.NSSecureCodingKey<T>,
options: ObservationOptions = [.initial],
handler: @escaping (NSSecureCodingKeyChange<T>) -> Void
) -> Defaults.Observation
```
```swift
Defaults.observe<T: NSSecureCoding>(
_ key: Defaults.NSSecureCodingOptionalKey<T>,
options: ObservationOptions = [.initial],
handler: @escaping (NSSecureCodingOptionalKeyChange<T>) -> Void
) -> Defaults.Observation
```
Type: `func`
Observe changes to a key or an optional key.
@ -420,20 +658,6 @@ Defaults.publisher<T: Codable>(
) -> AnyPublisher<KeyChange<T>, Never>
```
```swift
Defaults.publisher<T: NSSecureCoding>(
_ key: Defaults.NSSecureCodingKey<T>,
options: ObservationOptions = [.initial]
) -> AnyPublisher<NSSecureCodingKeyChange<T>, Never>
```
```swift
Defaults.publisher<T: NSSecureCoding>(
_ key: Defaults.NSSecureCodingOptionalKey<T>,
options: ObservationOptions = [.initial]
) -> AnyPublisher<NSSecureCodingOptionalKeyChange<T>, Never>
```
Type: `func`
Observation API using [Publisher](https://developer.apple.com/documentation/combine/publisher) from the [Combine](https://developer.apple.com/documentation/combine) framework.
@ -505,21 +729,71 @@ Execute the closure without triggering change events.
Any `Defaults` key changes made within the closure will not propagate to `Defaults` event listeners (`Defaults.observe()` and `Defaults.publisher()`). This can be useful to prevent infinite recursion when you want to change a key in the callback listening to changes for the same key.
#### `Defaults.migrate(keys..., to: Version)`
```swift
Defaults.migrate<T: Defaults.Serializable & Codable>(keys..., to: Version)
Defaults.migrate<T: Defaults.NativeType>(keys..., to: Version)
```
Type: `func`
Migrate the given keys to the specific version.
You can specify up to 10 keys. If you need to specify more, call this method multiple times.
### `@Default(_ key:)`
Get/set a `Defaults` item and also have the view be updated when the value changes.
This is only implemented for `Defaults.Key`. PR welcome for `Defaults.NSSecureCoding` if you need it.
### Advanced
#### `Defaults.CollectionSerializable`
```swift
public protocol DefaultsCollectionSerializable: Collection, Defaults.Serializable {
init(_ elements: [Element])
}
```
Type: `protocol`
A `Collection` which can store into the native `UserDefaults`.
It should have an initializer `init(_ elements: [Element])` to let `Defaults` do the de-serialization.
#### `Defaults.SetAlgebraSerializable`
```swift
public protocol DefaultsSetAlgebraSerializable: SetAlgebra, Defaults.Serializable {
func toArray() -> [Element]
}
```
Type: `protocol`
A `SetAlgebra` which can store into the native `UserDefaults`.
It should have a function `func toArray() -> [Element]` to let `Defaults` do the serialization.
## FAQ
### How can I store a dictionary of arbitrary values?
You cannot store `[String: Any]` directly as it cannot conform to `Codable`. However, you can use the [`AnyCodable`](https://github.com/Flight-School/AnyCodable) package to work around this `Codable` limitation:
After `Defaults` v5, you don't need to use `Codable` to store dictionary, `Defaults` supports storing dictionary natively.
For `Defaults` support types, see [Support types](#support-types).
There might be situations where you want to use `[String: Any]` directly.
Unfortunately, since `Any` can not conform to `Defaults.Serializable`, `Defaults` can not support it.
However, you can use the [`AnyCodable`](https://github.com/Flight-School/AnyCodable) package to work around this `Defaults.Serializable` limitation:
```swift
import AnyCodable
/// Important: Let AnyCodable conforms to Defaults.Serializable
extension AnyCodable: Defaults.Serializable {}
extension Defaults.Keys {
static let magic = Key<[String: AnyCodable]>("magic", default: [:])
}