Add a `Defaults.Serializable` protocol (#57)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
f64b3ca562
commit
534157f1b5
|
@ -5,9 +5,20 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: macos-latest
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -8,6 +8,79 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
52D6D9871BEFF229002C0205 /* Defaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52D6D97C1BEFF229002C0205 /* Defaults.framework */; };
|
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 */; };
|
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8933C7841EB5B820000D00A4 /* Defaults.swift */; };
|
||||||
8933C7861EB5B820000D00A4 /* 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 */; };
|
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; };
|
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; };
|
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; };
|
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; };
|
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; };
|
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>"; };
|
AD2FAA261CD0B6D800659CF4 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
|
||||||
|
@ -179,6 +275,25 @@
|
||||||
path = Configs;
|
path = Configs;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
8933C7811EB5B7E0000D00A4 /* Sources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -191,6 +306,22 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
8933C7891EB5B82A000D00A4 /* DefaultsTests.swift */,
|
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;
|
name = Tests;
|
||||||
path = Tests/DefaultsTests;
|
path = Tests/DefaultsTests;
|
||||||
|
@ -215,6 +346,7 @@
|
||||||
E30E93D822E9425E00530C8F /* Defaults */ = {
|
E30E93D822E9425E00530C8F /* Defaults */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
71056FF025DE6DEF00524EDA /* Migration */,
|
||||||
8933C7841EB5B820000D00A4 /* Defaults.swift */,
|
8933C7841EB5B820000D00A4 /* Defaults.swift */,
|
||||||
E339B3B72449F10D00E7A40A /* UserDefaults.swift */,
|
E339B3B72449F10D00E7A40A /* UserDefaults.swift */,
|
||||||
E339B3B22449ED2000E7A40A /* Reset.swift */,
|
E339B3B22449ED2000E7A40A /* Reset.swift */,
|
||||||
|
@ -222,6 +354,9 @@
|
||||||
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
E286D0C623B8D51100570D1E /* Observation+Combine.swift */,
|
||||||
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */,
|
E38C9F26244ADA2F00A6737A /* SwiftUI.swift */,
|
||||||
E3EB3E32216505920033B089 /* Utilities.swift */,
|
E3EB3E32216505920033B089 /* Utilities.swift */,
|
||||||
|
718B783225917CCA004FF90D /* Defaults+Protocol.swift */,
|
||||||
|
718B783E25917D09004FF90D /* Defaults+Extensions.swift */,
|
||||||
|
719F5E1F258AFB2F004540F6 /* Defaults+Bridge.swift */,
|
||||||
);
|
);
|
||||||
path = Defaults;
|
path = Defaults;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -264,6 +399,7 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Defaults-iOS" */;
|
buildConfigurationList = 52D6D9901BEFF229002C0205 /* Build configuration list for PBXNativeTarget "Defaults-iOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
71A71338260497FF004095EE /* SwiftLint */,
|
||||||
52D6D9771BEFF229002C0205 /* Sources */,
|
52D6D9771BEFF229002C0205 /* Sources */,
|
||||||
52D6D9781BEFF229002C0205 /* Frameworks */,
|
52D6D9781BEFF229002C0205 /* Frameworks */,
|
||||||
52D6D9791BEFF229002C0205 /* Headers */,
|
52D6D9791BEFF229002C0205 /* Headers */,
|
||||||
|
@ -300,6 +436,7 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "Defaults-watchOS" */;
|
buildConfigurationList = 52D6D9E71BEFFF6E002C0205 /* Build configuration list for PBXNativeTarget "Defaults-watchOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
71A713532604A090004095EE /* SwiftLint */,
|
||||||
52D6D9DD1BEFFF6E002C0205 /* Sources */,
|
52D6D9DD1BEFFF6E002C0205 /* Sources */,
|
||||||
52D6D9DE1BEFFF6E002C0205 /* Frameworks */,
|
52D6D9DE1BEFFF6E002C0205 /* Frameworks */,
|
||||||
52D6D9DF1BEFFF6E002C0205 /* Headers */,
|
52D6D9DF1BEFFF6E002C0205 /* Headers */,
|
||||||
|
@ -318,6 +455,7 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "Defaults-tvOS" */;
|
buildConfigurationList = 52D6DA011BEFFFBE002C0205 /* Build configuration list for PBXNativeTarget "Defaults-tvOS" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
71A713472604A05A004095EE /* SwiftLint */,
|
||||||
52D6D9EB1BEFFFBE002C0205 /* Sources */,
|
52D6D9EB1BEFFFBE002C0205 /* Sources */,
|
||||||
52D6D9EC1BEFFFBE002C0205 /* Frameworks */,
|
52D6D9EC1BEFFFBE002C0205 /* Frameworks */,
|
||||||
52D6D9ED1BEFFFBE002C0205 /* Headers */,
|
52D6D9ED1BEFFFBE002C0205 /* Headers */,
|
||||||
|
@ -365,6 +503,8 @@
|
||||||
DD7502811C68FCFC006590AF /* PBXTargetDependency */,
|
DD7502811C68FCFC006590AF /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = "Defaults-macOS Tests";
|
name = "Defaults-macOS Tests";
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
productName = "Defaults-OS Tests";
|
productName = "Defaults-OS Tests";
|
||||||
productReference = DD75027A1C68FCFC006590AF /* Defaults-macOS Tests.xctest */;
|
productReference = DD75027A1C68FCFC006590AF /* Defaults-macOS Tests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
@ -436,6 +576,8 @@
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 52D6D9721BEFF229002C0205;
|
mainGroup = 52D6D9721BEFF229002C0205;
|
||||||
|
packageReferences = (
|
||||||
|
);
|
||||||
productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */;
|
productRefGroup = 52D6D97D1BEFF229002C0205 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
|
@ -504,6 +646,60 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase 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 */ = {
|
E3FD0A4B25BDA35F0011D293 /* SwiftLint */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -529,13 +725,20 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
E286D0C823B8D54C00570D1E /* Observation+Combine.swift in Sources */,
|
||||||
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
E38C9F28244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
8933C7851EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||||
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3B92449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
|
719F5E21258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||||
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
E3EB3E35216507AE0033B089 /* Observation.swift in Sources */,
|
||||||
E3EB3E33216505920033B089 /* Utilities.swift in Sources */,
|
E3EB3E33216505920033B089 /* Utilities.swift in Sources */,
|
||||||
|
718B784025917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||||
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
|
E339B3B42449ED2000E7A40A /* Reset.swift in Sources */,
|
||||||
|
718B783425917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -543,7 +746,22 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -551,13 +769,20 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
E286D0CA23B8D54E00570D1E /* Observation+Combine.swift in Sources */,
|
||||||
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
E38C9F2A244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
E3EB3E3A216507C40033B089 /* Utilities.swift in Sources */,
|
E3EB3E3A216507C40033B089 /* Utilities.swift in Sources */,
|
||||||
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3BB2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
|
719F5E23258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||||
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
E3EB3E37216507B50033B089 /* Observation.swift in Sources */,
|
||||||
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
|
8933C7871EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||||
|
718B784225917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||||
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */,
|
E339B3B62449ED2000E7A40A /* Reset.swift in Sources */,
|
||||||
|
718B783625917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -565,13 +790,20 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
E286D0C923B8D54D00570D1E /* Observation+Combine.swift in Sources */,
|
||||||
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
E38C9F29244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
E3EB3E3B216507C40033B089 /* Utilities.swift in Sources */,
|
E3EB3E3B216507C40033B089 /* Utilities.swift in Sources */,
|
||||||
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3BA2449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
|
719F5E22258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||||
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
E3EB3E38216507B60033B089 /* Observation.swift in Sources */,
|
||||||
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
|
8933C7881EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||||
|
718B784125917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||||
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */,
|
E339B3B52449ED2000E7A40A /* Reset.swift in Sources */,
|
||||||
|
718B783525917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -581,11 +813,18 @@
|
||||||
files = (
|
files = (
|
||||||
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
E286D0C723B8D51100570D1E /* Observation+Combine.swift in Sources */,
|
||||||
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
E38C9F27244ADA2F00A6737A /* SwiftUI.swift in Sources */,
|
||||||
|
7168638325E886DB00F55131 /* Migration+Defaults.swift in Sources */,
|
||||||
E3EB3E39216507C30033B089 /* Utilities.swift in Sources */,
|
E3EB3E39216507C30033B089 /* Utilities.swift in Sources */,
|
||||||
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
E339B3B82449F10D00E7A40A /* UserDefaults.swift in Sources */,
|
||||||
|
719F5E20258AFB2F004540F6 /* Defaults+Bridge.swift in Sources */,
|
||||||
E3EB3E36216507B50033B089 /* Observation.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 */,
|
8933C7861EB5B820000D00A4 /* Defaults.swift in Sources */,
|
||||||
|
718B783F25917D09004FF90D /* Defaults+Extensions.swift in Sources */,
|
||||||
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */,
|
E339B3B32449ED2000E7A40A /* Reset.swift in Sources */,
|
||||||
|
71056FF625DE6DEF00524EDA /* Migration+UserDefaults.swift in Sources */,
|
||||||
|
718B783325917CCA004FF90D /* Defaults+Protocol.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -593,7 +832,22 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -601,7 +855,22 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
codeCoverageEnabled = "YES">
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
@ -39,7 +40,6 @@
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO"
|
skipped = "NO"
|
||||||
parallelizable = "YES"
|
|
||||||
testExecutionOrdering = "random">
|
testExecutionOrdering = "random">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO"
|
skipped = "NO"
|
||||||
parallelizable = "YES"
|
|
||||||
testExecutionOrdering = "random">
|
testExecutionOrdering = "random">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO"
|
skipped = "NO"
|
||||||
parallelizable = "YES"
|
|
||||||
testExecutionOrdering = "random">
|
testExecutionOrdering = "random">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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 {}
|
|
@ -16,16 +16,15 @@ extension DefaultsBaseKey {
|
||||||
public enum Defaults {
|
public enum Defaults {
|
||||||
public typealias BaseKey = DefaultsBaseKey
|
public typealias BaseKey = DefaultsBaseKey
|
||||||
public typealias AnyKey = Keys
|
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 class Keys: BaseKey {
|
||||||
public typealias Key = Defaults.Key
|
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 name: String
|
||||||
public let suite: UserDefaults
|
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
|
public let defaultValue: Value
|
||||||
|
|
||||||
/// Create a defaults key.
|
/// Create a defaults key.
|
||||||
|
@ -49,67 +48,16 @@ public enum Defaults {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
|
guard let serialized = Value.toSerializable(defaultValue) else {
|
||||||
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 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the default value in the actual UserDefaults, so it can be used in other contexts, like binding.
|
// 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: [self.name: serialized])
|
||||||
suite.register(defaults: [key: defaultValue])
|
|
||||||
} else if let value = try? NSKeyedArchiver.archivedData(withRootObject: defaultValue, requiringSecureCoding: true) {
|
|
||||||
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 static subscript<Value: Serializable>(key: Key<Value>) -> Value {
|
||||||
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? {
|
|
||||||
get { key.suite[key] }
|
get { key.suite[key] }
|
||||||
set {
|
set {
|
||||||
key.suite[key] = newValue
|
key.suite[key] = newValue
|
||||||
|
@ -129,14 +77,7 @@ extension Defaults {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Defaults.Key {
|
extension Defaults.Key {
|
||||||
public convenience init<T>(_ key: String, suite: UserDefaults = .standard) where Value == T? {
|
public convenience init<T: Defaults.Serializable>(_ 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) {
|
|
||||||
self.init(key, default: nil, suite: suite)
|
self.init(key, default: nil, suite: suite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, *)
|
@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>,
|
_ key: Key<Value>,
|
||||||
options: ObservationOptions = [.initial]
|
options: ObservationOptions = [.initial]
|
||||||
) -> AnyPublisher<KeyChange<Value>, Never> {
|
) -> AnyPublisher<KeyChange<Value>, Never> {
|
||||||
|
@ -98,34 +98,6 @@ extension Defaults {
|
||||||
return AnyPublisher(publisher)
|
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.
|
Publisher for multiple `Key<T>` observation, but without specific information about changes.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -37,7 +37,7 @@ extension Defaults {
|
||||||
|
|
||||||
public typealias ObservationOptions = Set<ObservationOption>
|
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
|
guard
|
||||||
let value = value,
|
let value = value,
|
||||||
!(value is NSNull)
|
!(value is NSNull)
|
||||||
|
@ -45,34 +45,7 @@ extension Defaults {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This handles the case where the value was a plist value using `isNativelySupportedType`
|
return Value.toValue(value)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BaseChange {
|
struct BaseChange {
|
||||||
|
@ -91,7 +64,7 @@ extension Defaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct KeyChange<Value: Codable> {
|
public struct KeyChange<Value: Serializable> {
|
||||||
public let kind: NSKeyValueChange
|
public let kind: NSKeyValueChange
|
||||||
public let indexes: IndexSet?
|
public let indexes: IndexSet?
|
||||||
public let isPrior: Bool
|
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 {
|
private static var preventPropagationThreadDictionaryKey: String {
|
||||||
"\(type(of: Observation.self))_threadUpdatingValuesFlag"
|
"\(type(of: Observation.self))_threadUpdatingValuesFlag"
|
||||||
}
|
}
|
||||||
|
@ -242,7 +181,6 @@ extension Defaults {
|
||||||
guard !updatingValuesFlag else {
|
guard !updatingValuesFlag else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(BaseChange(change: change))
|
callback(BaseChange(change: change))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +290,7 @@ extension Defaults {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public static func observe<Value>(
|
public static func observe<Value: Serializable>(
|
||||||
_ key: Key<Value>,
|
_ key: Key<Value>,
|
||||||
options: ObservationOptions = [.initial],
|
options: ObservationOptions = [.initial],
|
||||||
handler: @escaping (KeyChange<Value>) -> Void
|
handler: @escaping (KeyChange<Value>) -> Void
|
||||||
|
@ -366,41 +304,6 @@ extension Defaults {
|
||||||
return observation
|
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.
|
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 { }
|
extension Defaults.KeyChange: Equatable where Value: Equatable { }
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Combine
|
||||||
|
|
||||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
extension Defaults {
|
extension Defaults {
|
||||||
final class Observable<Value: Codable>: ObservableObject {
|
final class Observable<Value: Serializable>: ObservableObject {
|
||||||
let objectWillChange = ObservableObjectPublisher()
|
let objectWillChange = ObservableObjectPublisher()
|
||||||
private var observation: DefaultsObservation?
|
private var observation: DefaultsObservation?
|
||||||
private let key: Defaults.Key<Value>
|
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, *)
|
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct Default<Value: Codable>: DynamicProperty {
|
public struct Default<Value: Defaults.Serializable>: DynamicProperty {
|
||||||
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
|
public typealias Publisher = AnyPublisher<Defaults.KeyChange<Value>, Never>
|
||||||
|
|
||||||
private let key: Defaults.Key<Value>
|
private let key: Defaults.Key<Value>
|
||||||
|
|
|
@ -1,136 +1,29 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
private func _get<Value: Codable>(_ key: String) -> Value? {
|
func _get<Value: Defaults.Serializable>(_ key: String) -> Value? {
|
||||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
guard let anyObject = object(forKey: key) else {
|
||||||
return object(forKey: key) as? Value
|
|
||||||
}
|
|
||||||
|
|
||||||
guard
|
|
||||||
let text = string(forKey: key),
|
|
||||||
let data = "[\(text)]".data(using: .utf8)
|
|
||||||
else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
return Value.toValue(anyObject)
|
||||||
return (try JSONDecoder().decode([Value].self, from: data)).first
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 _set<Value: Defaults.Serializable>(_ key: String, to value: Value) {
|
||||||
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) {
|
|
||||||
if (value as? _DefaultsOptionalType)?.isNil == true {
|
if (value as? _DefaultsOptionalType)?.isNil == true {
|
||||||
removeObject(forKey: key)
|
removeObject(forKey: key)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if UserDefaults.isNativelySupportedType(Value.self) {
|
set(Value.toSerializable(value), forKey: key)
|
||||||
set(value, forKey: key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
set(_encode(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, *)
|
public subscript<Value: Defaults.Serializable>(key: Defaults.Key<Value>) -> Value {
|
||||||
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 {
|
|
||||||
get { _get(key.name) ?? key.defaultValue }
|
get { _get(key.name) ?? key.defaultValue }
|
||||||
set {
|
set {
|
||||||
_set(key.name, to: newValue)
|
_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 {
|
extension UserDefaults {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,53 +1,20 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
|
||||||
import Combine
|
import Combine
|
||||||
import XCTest
|
import XCTest
|
||||||
import Defaults
|
import Defaults
|
||||||
|
|
||||||
let fixtureURL = URL(string: "https://sindresorhus.com")!
|
let fixtureURL = URL(string: "https://sindresorhus.com")!
|
||||||
|
let fixtureFileURL = URL(string: "file://~/icon.png")!
|
||||||
let fixtureURL2 = URL(string: "https://example.com")!
|
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()
|
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 {
|
extension Defaults.Keys {
|
||||||
static let key = Key<Bool>("key", default: false)
|
static let key = Key<Bool>("key", default: false)
|
||||||
static let url = Key<URL>("url", default: fixtureURL)
|
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 data = Key<Data>("data", default: Data([]))
|
||||||
static let date = Key<Date>("date", default: fixtureDate)
|
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 {
|
final class DefaultsTests: XCTestCase {
|
||||||
|
@ -70,13 +37,21 @@ final class DefaultsTests: XCTestCase {
|
||||||
|
|
||||||
func testOptionalKey() {
|
func testOptionalKey() {
|
||||||
let key = Defaults.Key<Bool?>("independentOptionalKey")
|
let key = Defaults.Key<Bool?>("independentOptionalKey")
|
||||||
|
let url = Defaults.Key<URL?>("independentOptionalURLKey")
|
||||||
XCTAssertNil(Defaults[key])
|
XCTAssertNil(Defaults[key])
|
||||||
|
XCTAssertNil(Defaults[url])
|
||||||
Defaults[key] = true
|
Defaults[key] = true
|
||||||
|
Defaults[url] = fixtureURL
|
||||||
XCTAssertTrue(Defaults[key]!)
|
XCTAssertTrue(Defaults[key]!)
|
||||||
|
XCTAssertEqual(Defaults[url], fixtureURL)
|
||||||
Defaults[key] = nil
|
Defaults[key] = nil
|
||||||
|
Defaults[url] = nil
|
||||||
XCTAssertNil(Defaults[key])
|
XCTAssertNil(Defaults[key])
|
||||||
|
XCTAssertNil(Defaults[url])
|
||||||
Defaults[key] = false
|
Defaults[key] = false
|
||||||
|
Defaults[url] = fixtureURL2
|
||||||
XCTAssertFalse(Defaults[key]!)
|
XCTAssertFalse(Defaults[key]!)
|
||||||
|
XCTAssertEqual(Defaults[url], fixtureURL2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testKeyRegistersDefault() {
|
func testKeyRegistersDefault() {
|
||||||
|
@ -104,29 +79,15 @@ final class DefaultsTests: XCTestCase {
|
||||||
XCTAssertTrue(Defaults[.key])
|
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() {
|
func testUrlType() {
|
||||||
XCTAssertEqual(Defaults[.url], fixtureURL)
|
XCTAssertEqual(Defaults[.url], fixtureURL)
|
||||||
|
|
||||||
let newUrl = URL(string: "https://twitter.com")!
|
let newUrl = URL(string: "https://twitter.com")!
|
||||||
Defaults[.url] = newUrl
|
Defaults[.url] = newUrl
|
||||||
XCTAssertEqual(Defaults[.url], newUrl)
|
XCTAssertEqual(Defaults[.url], newUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEnumType() {
|
|
||||||
XCTAssertEqual(Defaults[.enum], FixtureEnum.oneHour)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDataType() {
|
func testDataType() {
|
||||||
XCTAssertEqual(Defaults[.data], Data([]))
|
XCTAssertEqual(Defaults[.data], Data([]))
|
||||||
|
|
||||||
let newData = Data([0xFF])
|
let newData = Data([0xFF])
|
||||||
Defaults[.data] = newData
|
Defaults[.data] = newData
|
||||||
XCTAssertEqual(Defaults[.data], newData)
|
XCTAssertEqual(Defaults[.data], newData)
|
||||||
|
@ -134,12 +95,15 @@ final class DefaultsTests: XCTestCase {
|
||||||
|
|
||||||
func testDateType() {
|
func testDateType() {
|
||||||
XCTAssertEqual(Defaults[.date], fixtureDate)
|
XCTAssertEqual(Defaults[.date], fixtureDate)
|
||||||
|
|
||||||
let newDate = Date()
|
let newDate = Date()
|
||||||
Defaults[.date] = newDate
|
Defaults[.date] = newDate
|
||||||
XCTAssertEqual(Defaults[.date], newDate)
|
XCTAssertEqual(Defaults[.date], newDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testFileURLType() {
|
||||||
|
XCTAssertEqual(Defaults[.file], fixtureFileURL)
|
||||||
|
}
|
||||||
|
|
||||||
func testRemoveAll() {
|
func testRemoveAll() {
|
||||||
let key = Defaults.Key<Bool>("removeAll", default: false)
|
let key = Defaults.Key<Bool>("removeAll", default: false)
|
||||||
let key2 = Defaults.Key<Bool>("removeAll2", default: false)
|
let key2 = Defaults.Key<Bool>("removeAll2", default: false)
|
||||||
|
@ -189,39 +153,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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, *)
|
@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() {
|
func testObserveOptionalKeyCombine() {
|
||||||
let key = Defaults.Key<Bool?>("observeOptionalKey")
|
let key = Defaults.Key<Bool?>("observeOptionalKey")
|
||||||
|
@ -251,40 +182,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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, *)
|
@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() {
|
func testObserveMultipleKeysCombine() {
|
||||||
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
||||||
|
@ -304,26 +201,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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, *)
|
@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() {
|
func testObserveMultipleOptionalKeysCombine() {
|
||||||
let key1 = Defaults.Key<String?>("observeOptionalKey1")
|
let key1 = Defaults.Key<String?>("observeOptionalKey1")
|
||||||
|
@ -343,25 +220,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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, *)
|
@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() {
|
func testReceiveValueBeforeSubscriptionCombine() {
|
||||||
let key = Defaults.Key<String>("receiveValueBeforeSubscription", default: "hello")
|
let key = Defaults.Key<String>("receiveValueBeforeSubscription", default: "hello")
|
||||||
|
@ -400,24 +258,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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() {
|
func testObserveOptionalKey() {
|
||||||
let key = Defaults.Key<Bool?>("observeOptionalKey")
|
let key = Defaults.Key<Bool?>("observeOptionalKey")
|
||||||
let expect = expectation(description: "Observation closure being called")
|
let expect = expectation(description: "Observation closure being called")
|
||||||
|
@ -435,24 +275,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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() {
|
func testObserveMultipleKeys() {
|
||||||
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
let key1 = Defaults.Key<String>("observeKey1", default: "x")
|
||||||
let key2 = Defaults.Key<Bool>("observeKey2", default: true)
|
let key2 = Defaults.Key<Bool>("observeKey2", default: true)
|
||||||
|
@ -476,33 +298,7 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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() {
|
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 key = Defaults.Key<URL>("observeKeyURL", default: fixtureURL)
|
||||||
let expect = expectation(description: "Observation closure being called")
|
let expect = expectation(description: "Observation closure being called")
|
||||||
|
|
||||||
|
@ -519,23 +315,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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() {
|
func testObservePreventPropagation() {
|
||||||
let key1 = Defaults.Key<Bool?>("preventPropagation0", default: nil)
|
let key1 = Defaults.Key<Bool?>("preventPropagation0", default: nil)
|
||||||
let expect = expectation(description: "No infinite recursion")
|
let expect = expectation(description: "No infinite recursion")
|
||||||
|
@ -594,7 +373,7 @@ final class DefaultsTests: XCTestCase {
|
||||||
XCTAssert(Defaults[key1]! == 4)
|
XCTAssert(Defaults[key1]! == 4)
|
||||||
expect.fulfill()
|
expect.fulfill()
|
||||||
} else {
|
} else {
|
||||||
usleep(100_000)
|
usleep(300_000)
|
||||||
print("--- Release: \(Thread.isMainThread)")
|
print("--- Release: \(Thread.isMainThread)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -699,120 +478,6 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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, *)
|
@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() {
|
func testRemoveDuplicatesObserveKeyCombine() {
|
||||||
let key = Defaults.Key<Bool>("observeKey", default: false)
|
let key = Defaults.Key<Bool>("observeKey", default: false)
|
||||||
|
@ -869,61 +534,88 @@ final class DefaultsTests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
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 testResetKey() {
|
||||||
func testRemoveDuplicatesObserveNSSecureCodingKeyCombine() {
|
let defaultFixture1 = "foo1"
|
||||||
let key = Defaults.NSSecureCodingKey<ExamplePersistentHistory>("observeNSSecureCodingKey", default: ExamplePersistentHistory(value: "TestValue"))
|
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 expect = expectation(description: "Observation closure being called")
|
||||||
|
|
||||||
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
|
weak var observation: Defaults.Observation!
|
||||||
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3"]
|
observation = Defaults.observe(key, options: []) { _ in
|
||||||
|
observation.invalidate()
|
||||||
let cancellable = Defaults
|
expect.fulfill()
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
|
.tieToLifetime(of: self)
|
||||||
|
|
||||||
Defaults.reset(key)
|
Defaults[key] = true
|
||||||
cancellable.cancel()
|
|
||||||
|
|
||||||
waitForExpectations(timeout: 10)
|
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 testObserveWithLifetimeTieManualBreak() {
|
||||||
func testRemoveDuplicatesObserveNSSecureCodingOptionalKeyCombine() {
|
let key = Defaults.Key<Bool>("lifetimeTieManualBreak", default: false)
|
||||||
let key = Defaults.NSSecureCodingOptionalKey<ExamplePersistentHistory>("observeNSSecureCodingOptionalKey")
|
|
||||||
let expect = expectation(description: "Observation closure being called")
|
|
||||||
|
|
||||||
let inputArray = ["NewTestValue", "NewTestValue", "NewTestValue", "NewTestValue2", "NewTestValue2", "NewTestValue2", "NewTestValue3", "NewTestValue3"]
|
weak var observation: Defaults.Observation? = Defaults.observe(key, options: []) { _ in }.tieToLifetime(of: self)
|
||||||
let expectedArray = ["NewTestValue", "NewTestValue2", "NewTestValue3", nil]
|
observation!.removeLifetimeTie()
|
||||||
|
|
||||||
let cancellable = Defaults
|
for index in 1...10 {
|
||||||
.publisher(key, options: [])
|
if observation == nil {
|
||||||
.removeDuplicates()
|
break
|
||||||
.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 {
|
sleep(1)
|
||||||
Defaults[key] = ExamplePersistentHistory(value: $0)
|
|
||||||
|
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, *)
|
@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, *)
|
||||||
|
|
|
@ -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
|
|
@ -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
404
readme.md
|
@ -19,6 +19,7 @@ For a real-world example, see my [Plash app](https://github.com/sindresorhus/Pla
|
||||||
- **Publishers:** Combine publishers built-in.
|
- **Publishers:** Combine publishers built-in.
|
||||||
- **Observation:** Observe changes to keys.
|
- **Observation:** Observe changes to keys.
|
||||||
- **Debuggable:** The data is stored as JSON-serialized values.
|
- **Debuggable:** The data is stored as JSON-serialized values.
|
||||||
|
- **Customizable:** You can serialize and deserialize your own type in your own way.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
@ -27,6 +28,10 @@ For a real-world example, see my [Plash app](https://github.com/sindresorhus/Pla
|
||||||
- tvOS 10+
|
- tvOS 10+
|
||||||
- watchOS 3+
|
- watchOS 3+
|
||||||
|
|
||||||
|
## Migration Guides
|
||||||
|
|
||||||
|
#### [From v4 to v5](./migration.md)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
#### Swift Package Manager
|
#### Swift Package Manager
|
||||||
|
@ -45,6 +50,29 @@ github "sindresorhus/Defaults"
|
||||||
pod '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
|
## Usage
|
||||||
|
|
||||||
You declare the defaults keys upfront with type and default value.
|
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
|
### Enum example
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
enum DurationKeys: String, Codable {
|
enum DurationKeys: String, Defaults.Serializable {
|
||||||
case tenMinutes = "10 Minutes"
|
case tenMinutes = "10 Minutes"
|
||||||
case halfHour = "30 Minutes"
|
case halfHour = "30 Minutes"
|
||||||
case oneHour = "1 Hour"
|
case oneHour = "1 Hour"
|
||||||
|
@ -129,6 +137,22 @@ Defaults[.defaultDuration].rawValue
|
||||||
//=> "1 Hour"
|
//=> "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
|
### Use keys directly
|
||||||
|
|
||||||
You are not required to attach keys to `Defaults.Keys`.
|
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`.
|
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
|
### Observe changes to a key
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
@ -317,6 +339,217 @@ print(UserDefaults.standard.bool(forKey: Defaults.Keys.isUnicornMode.name))
|
||||||
//=> true
|
//=> 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
|
## API
|
||||||
|
|
||||||
### `Defaults`
|
### `Defaults`
|
||||||
|
@ -327,7 +560,7 @@ Type: `class`
|
||||||
|
|
||||||
Stores the keys.
|
Stores the keys.
|
||||||
|
|
||||||
#### `Defaults.Key` *(alias `Defaults.Keys.Key`)*
|
#### `Defaults.Key` _(alias `Defaults.Keys.Key`)_
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Defaults.Key<T>(_ key: String, default: T, suite: UserDefaults = .standard)
|
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.
|
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
|
```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
|
```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…)`
|
#### `Defaults.reset(keys…)`
|
||||||
|
|
||||||
|
@ -381,22 +635,6 @@ Defaults.observe<T: Codable>(
|
||||||
) -> Defaults.Observation
|
) -> 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`
|
Type: `func`
|
||||||
|
|
||||||
Observe changes to a key or an optional key.
|
Observe changes to a key or an optional key.
|
||||||
|
@ -420,20 +658,6 @@ Defaults.publisher<T: Codable>(
|
||||||
) -> AnyPublisher<KeyChange<T>, Never>
|
) -> 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`
|
Type: `func`
|
||||||
|
|
||||||
Observation API using [Publisher](https://developer.apple.com/documentation/combine/publisher) from the [Combine](https://developer.apple.com/documentation/combine) framework.
|
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.
|
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:)`
|
### `@Default(_ key:)`
|
||||||
|
|
||||||
Get/set a `Defaults` item and also have the view be updated when the value changes.
|
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
|
## FAQ
|
||||||
|
|
||||||
### How can I store a dictionary of arbitrary values?
|
### 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
|
```swift
|
||||||
import AnyCodable
|
import AnyCodable
|
||||||
|
|
||||||
|
/// Important: Let AnyCodable conforms to Defaults.Serializable
|
||||||
|
extension AnyCodable: Defaults.Serializable {}
|
||||||
|
|
||||||
extension Defaults.Keys {
|
extension Defaults.Keys {
|
||||||
static let magic = Key<[String: AnyCodable]>("magic", default: [:])
|
static let magic = Key<[String: AnyCodable]>("magic", default: [:])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue