mirror of
https://github.com/hyperoslo/Cache.git
synced 2026-04-07 19:17:36 +00:00
Merge pull request #189 from hyperoslo/refactor/generic_storage
Refactor to generic Storage
This commit is contained in:
+138
-100
@@ -9,31 +9,17 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
BDEDD37D1DBCEB8A007416A6 /* Cache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDEDD3561DBCE5B1007416A6 /* Cache.framework */; };
|
||||
D21B667C1F6A723C00125DE1 /* DataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98531F694FFA00CE8F68 /* DataSerializer.swift */; };
|
||||
D21B667D1F6A723C00125DE1 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98541F694FFA00CE8F68 /* Entry.swift */; };
|
||||
D21B667E1F6A723C00125DE1 /* ExpirationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98551F694FFA00CE8F68 /* ExpirationMode.swift */; };
|
||||
D21B667F1F6A723C00125DE1 /* Expiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98561F694FFA00CE8F68 /* Expiry.swift */; };
|
||||
D21B66801F6A723C00125DE1 /* ImageWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98571F694FFA00CE8F68 /* ImageWrapper.swift */; };
|
||||
D21B66821F6A723C00125DE1 /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98591F694FFA00CE8F68 /* StorageError.swift */; };
|
||||
D21B66831F6A723C00125DE1 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98861F695B8F00CE8F68 /* Types.swift */; };
|
||||
D21B66851F6A723C00125DE1 /* DataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98531F694FFA00CE8F68 /* DataSerializer.swift */; };
|
||||
D21B66861F6A723C00125DE1 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98541F694FFA00CE8F68 /* Entry.swift */; };
|
||||
D21B66871F6A723C00125DE1 /* ExpirationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98551F694FFA00CE8F68 /* ExpirationMode.swift */; };
|
||||
D21B66881F6A723C00125DE1 /* Expiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98561F694FFA00CE8F68 /* Expiry.swift */; };
|
||||
D21B66891F6A723C00125DE1 /* ImageWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98571F694FFA00CE8F68 /* ImageWrapper.swift */; };
|
||||
D21B668B1F6A723C00125DE1 /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98591F694FFA00CE8F68 /* StorageError.swift */; };
|
||||
D21B668C1F6A723C00125DE1 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98861F695B8F00CE8F68 /* Types.swift */; };
|
||||
D21B668D1F6A723F00125DE1 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985B1F694FFA00CE8F68 /* DiskStorage.swift */; };
|
||||
D21B668E1F6A723F00125DE1 /* HybridStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985C1F694FFA00CE8F68 /* HybridStorage.swift */; };
|
||||
D21B668F1F6A723F00125DE1 /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985D1F694FFA00CE8F68 /* MemoryStorage.swift */; };
|
||||
D21B66901F6A723F00125DE1 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985E1F694FFA00CE8F68 /* Storage.swift */; };
|
||||
D21B66911F6A723F00125DE1 /* StorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985F1F694FFA00CE8F68 /* StorageAware.swift */; };
|
||||
D21B66921F6A723F00125DE1 /* TypeWrapperStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98841F69598900CE8F68 /* TypeWrapperStorage.swift */; };
|
||||
D21B66931F6A724000125DE1 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985B1F694FFA00CE8F68 /* DiskStorage.swift */; };
|
||||
D21B66941F6A724000125DE1 /* HybridStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985C1F694FFA00CE8F68 /* HybridStorage.swift */; };
|
||||
D21B66951F6A724000125DE1 /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985D1F694FFA00CE8F68 /* MemoryStorage.swift */; };
|
||||
D21B66961F6A724000125DE1 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985E1F694FFA00CE8F68 /* Storage.swift */; };
|
||||
D21B66971F6A724000125DE1 /* StorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985F1F694FFA00CE8F68 /* StorageAware.swift */; };
|
||||
D21B66981F6A724000125DE1 /* TypeWrapperStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98841F69598900CE8F68 /* TypeWrapperStorage.swift */; };
|
||||
D21B66991F6A724200125DE1 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98501F694FFA00CE8F68 /* Date+Extensions.swift */; };
|
||||
D21B669A1F6A724300125DE1 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98501F694FFA00CE8F68 /* Date+Extensions.swift */; };
|
||||
D21B669B1F6A724600125DE1 /* DiskConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF984D1F694FFA00CE8F68 /* DiskConfig.swift */; };
|
||||
@@ -41,7 +27,60 @@
|
||||
D21B669D1F6A724600125DE1 /* DiskConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF984D1F694FFA00CE8F68 /* DiskConfig.swift */; };
|
||||
D21B669E1F6A724700125DE1 /* MemoryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF984E1F694FFA00CE8F68 /* MemoryConfig.swift */; };
|
||||
D21B66A11F6A747000125DE1 /* ImageWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98761F69513800CE8F68 /* ImageWrapperTests.swift */; };
|
||||
D236F31A1F6BEF73004EE01F /* StorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236F3191F6BEF73004EE01F /* StorageTests.swift */; };
|
||||
D221E5BC20D00D9300BC940E /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5BB20D00D9300BC940E /* MemoryStorage.swift */; };
|
||||
D221E5BD20D00D9300BC940E /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5BB20D00D9300BC940E /* MemoryStorage.swift */; };
|
||||
D221E5BE20D00D9300BC940E /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5BB20D00D9300BC940E /* MemoryStorage.swift */; };
|
||||
D221E5C020D00DCC00BC940E /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5BF20D00DCC00BC940E /* Entry.swift */; };
|
||||
D221E5C120D00DCC00BC940E /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5BF20D00DCC00BC940E /* Entry.swift */; };
|
||||
D221E5C220D00DCC00BC940E /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5BF20D00DCC00BC940E /* Entry.swift */; };
|
||||
D221E5C420D00DDB00BC940E /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5C320D00DDB00BC940E /* DiskStorage.swift */; };
|
||||
D221E5C520D00DDB00BC940E /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5C320D00DDB00BC940E /* DiskStorage.swift */; };
|
||||
D221E5C620D00DDB00BC940E /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D221E5C320D00DDB00BC940E /* DiskStorage.swift */; };
|
||||
D270147020D1018A003B45C7 /* Transformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270146F20D1018A003B45C7 /* Transformer.swift */; };
|
||||
D270147120D1018A003B45C7 /* Transformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270146F20D1018A003B45C7 /* Transformer.swift */; };
|
||||
D270147220D1018A003B45C7 /* Transformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270146F20D1018A003B45C7 /* Transformer.swift */; };
|
||||
D270147420D101F3003B45C7 /* StorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147320D101F3003B45C7 /* StorageAware.swift */; };
|
||||
D270147520D101F3003B45C7 /* StorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147320D101F3003B45C7 /* StorageAware.swift */; };
|
||||
D270147620D101F3003B45C7 /* StorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147320D101F3003B45C7 /* StorageAware.swift */; };
|
||||
D270147820D1046A003B45C7 /* HybridStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147720D1046A003B45C7 /* HybridStorage.swift */; };
|
||||
D270147920D1046A003B45C7 /* HybridStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147720D1046A003B45C7 /* HybridStorage.swift */; };
|
||||
D270147A20D1046A003B45C7 /* HybridStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147720D1046A003B45C7 /* HybridStorage.swift */; };
|
||||
D270147C20D107DA003B45C7 /* SyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147B20D107DA003B45C7 /* SyncStorage.swift */; };
|
||||
D270147D20D107DA003B45C7 /* SyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147B20D107DA003B45C7 /* SyncStorage.swift */; };
|
||||
D270147E20D107DA003B45C7 /* SyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147B20D107DA003B45C7 /* SyncStorage.swift */; };
|
||||
D270148020D10982003B45C7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147F20D10982003B45C7 /* Storage.swift */; };
|
||||
D270148120D10982003B45C7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147F20D10982003B45C7 /* Storage.swift */; };
|
||||
D270148220D10982003B45C7 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270147F20D10982003B45C7 /* Storage.swift */; };
|
||||
D270148420D10E76003B45C7 /* AsyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148320D10E76003B45C7 /* AsyncStorage.swift */; };
|
||||
D270148520D10E76003B45C7 /* AsyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148320D10E76003B45C7 /* AsyncStorage.swift */; };
|
||||
D270148620D10E76003B45C7 /* AsyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148320D10E76003B45C7 /* AsyncStorage.swift */; };
|
||||
D270148820D11040003B45C7 /* Storage+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148720D11040003B45C7 /* Storage+Transform.swift */; };
|
||||
D270148920D11040003B45C7 /* Storage+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148720D11040003B45C7 /* Storage+Transform.swift */; };
|
||||
D270148A20D11040003B45C7 /* Storage+Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148720D11040003B45C7 /* Storage+Transform.swift */; };
|
||||
D270148C20D113EA003B45C7 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148B20D113EA003B45C7 /* Optional+Extension.swift */; };
|
||||
D270148D20D113EA003B45C7 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148B20D113EA003B45C7 /* Optional+Extension.swift */; };
|
||||
D270148E20D113EA003B45C7 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148B20D113EA003B45C7 /* Optional+Extension.swift */; };
|
||||
D270149020D1251E003B45C7 /* TypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148F20D1251E003B45C7 /* TypeWrapper.swift */; };
|
||||
D270149120D1251E003B45C7 /* TypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148F20D1251E003B45C7 /* TypeWrapper.swift */; };
|
||||
D270149220D1251E003B45C7 /* TypeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270148F20D1251E003B45C7 /* TypeWrapper.swift */; };
|
||||
D270149420D125AC003B45C7 /* MemoryCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270149320D125AC003B45C7 /* MemoryCapsule.swift */; };
|
||||
D270149520D125AC003B45C7 /* MemoryCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270149320D125AC003B45C7 /* MemoryCapsule.swift */; };
|
||||
D270149620D125AC003B45C7 /* MemoryCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270149320D125AC003B45C7 /* MemoryCapsule.swift */; };
|
||||
D270149E20D1286F003B45C7 /* MemoryStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF987B1F69513800CE8F68 /* MemoryStorageTests.swift */; };
|
||||
D27014A020D12870003B45C7 /* MemoryStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF987B1F69513800CE8F68 /* MemoryStorageTests.swift */; };
|
||||
D27014A120D129A2003B45C7 /* DiskStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98791F69513800CE8F68 /* DiskStorageTests.swift */; };
|
||||
D27014A320D129A3003B45C7 /* DiskStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98791F69513800CE8F68 /* DiskStorageTests.swift */; };
|
||||
D27014A520D129EB003B45C7 /* TransformerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27014A420D129EB003B45C7 /* TransformerFactory.swift */; };
|
||||
D27014A620D129EB003B45C7 /* TransformerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27014A420D129EB003B45C7 /* TransformerFactory.swift */; };
|
||||
D27014A720D129EB003B45C7 /* TransformerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27014A420D129EB003B45C7 /* TransformerFactory.swift */; };
|
||||
D27014A820D12BA3003B45C7 /* HybridStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF987A1F69513800CE8F68 /* HybridStorageTests.swift */; };
|
||||
D27014AA20D12BA4003B45C7 /* HybridStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF987A1F69513800CE8F68 /* HybridStorageTests.swift */; };
|
||||
D27014AC20D12CC1003B45C7 /* SyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */; };
|
||||
D27014AD20D12CC3003B45C7 /* SyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */; };
|
||||
D27014AE20D12D83003B45C7 /* AsyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */; };
|
||||
D27014AF20D12D84003B45C7 /* AsyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */; };
|
||||
D27014B020D12E37003B45C7 /* StorageSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98881F695F9400CE8F68 /* StorageSupportTests.swift */; };
|
||||
D27014B120D12E38003B45C7 /* StorageSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98881F695F9400CE8F68 /* StorageSupportTests.swift */; };
|
||||
D28897051F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28897041F8B79B300C61DEE /* JSONDecoder+Extensions.swift */; };
|
||||
D28897061F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28897041F8B79B300C61DEE /* JSONDecoder+Extensions.swift */; };
|
||||
D28897071F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28897041F8B79B300C61DEE /* JSONDecoder+Extensions.swift */; };
|
||||
@@ -50,22 +89,9 @@
|
||||
D28A1D241F6FFEF60030DF81 /* ObjectConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D285143E1F6FFE1F00C674D1 /* ObjectConverterTests.swift */; };
|
||||
D28C9BAC1F67ECD400C180C1 /* TestHelper+iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5291CDF1C28374800B702C9 /* TestHelper+iOS.swift */; };
|
||||
D28C9BAF1F67EF8300C180C1 /* UIImage+ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5291DA01C28405900B702C9 /* UIImage+ExtensionsTests.swift */; };
|
||||
D292DAF11F6A85F30060F614 /* SyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF01F6A85F30060F614 /* SyncStorage.swift */; };
|
||||
D292DAF21F6A85F30060F614 /* SyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF01F6A85F30060F614 /* SyncStorage.swift */; };
|
||||
D292DAF31F6A85F30060F614 /* SyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF01F6A85F30060F614 /* SyncStorage.swift */; };
|
||||
D292DAF51F6A94000060F614 /* AsyncStorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF41F6A94000060F614 /* AsyncStorageAware.swift */; };
|
||||
D292DAF61F6A94000060F614 /* AsyncStorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF41F6A94000060F614 /* AsyncStorageAware.swift */; };
|
||||
D292DAF71F6A94000060F614 /* AsyncStorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF41F6A94000060F614 /* AsyncStorageAware.swift */; };
|
||||
D292DAF91F6A962D0060F614 /* AsyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF81F6A962D0060F614 /* AsyncStorage.swift */; };
|
||||
D292DAFA1F6A962D0060F614 /* AsyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF81F6A962D0060F614 /* AsyncStorage.swift */; };
|
||||
D292DAFB1F6A962D0060F614 /* AsyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAF81F6A962D0060F614 /* AsyncStorage.swift */; };
|
||||
D292DAFD1F6A970B0060F614 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAFC1F6A970B0060F614 /* Result.swift */; };
|
||||
D292DAFE1F6A970B0060F614 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAFC1F6A970B0060F614 /* Result.swift */; };
|
||||
D292DAFF1F6A970B0060F614 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DAFC1F6A970B0060F614 /* Result.swift */; };
|
||||
D292DB011F6AA06B0060F614 /* SyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */; };
|
||||
D292DB021F6AA06B0060F614 /* SyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */; };
|
||||
D292DB041F6AA0730060F614 /* AsyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */; };
|
||||
D292DB051F6AA0730060F614 /* AsyncStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */; };
|
||||
D2CF981E1F69427C00CE8F68 /* TestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF981B1F69427C00CE8F68 /* TestCase+Extensions.swift */; };
|
||||
D2CF981F1F69427C00CE8F68 /* TestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF981B1F69427C00CE8F68 /* TestCase+Extensions.swift */; };
|
||||
D2CF98201F69427C00CE8F68 /* TestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF981B1F69427C00CE8F68 /* TestCase+Extensions.swift */; };
|
||||
@@ -79,27 +105,16 @@
|
||||
D2CF98611F694FFA00CE8F68 /* MemoryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF984E1F694FFA00CE8F68 /* MemoryConfig.swift */; };
|
||||
D2CF98621F694FFA00CE8F68 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98501F694FFA00CE8F68 /* Date+Extensions.swift */; };
|
||||
D2CF98641F694FFA00CE8F68 /* DataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98531F694FFA00CE8F68 /* DataSerializer.swift */; };
|
||||
D2CF98651F694FFA00CE8F68 /* Entry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98541F694FFA00CE8F68 /* Entry.swift */; };
|
||||
D2CF98661F694FFA00CE8F68 /* ExpirationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98551F694FFA00CE8F68 /* ExpirationMode.swift */; };
|
||||
D2CF98671F694FFA00CE8F68 /* Expiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98561F694FFA00CE8F68 /* Expiry.swift */; };
|
||||
D2CF98681F694FFA00CE8F68 /* ImageWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98571F694FFA00CE8F68 /* ImageWrapper.swift */; };
|
||||
D2CF986A1F694FFA00CE8F68 /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98591F694FFA00CE8F68 /* StorageError.swift */; };
|
||||
D2CF986B1F694FFA00CE8F68 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985B1F694FFA00CE8F68 /* DiskStorage.swift */; };
|
||||
D2CF986C1F694FFA00CE8F68 /* HybridStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985C1F694FFA00CE8F68 /* HybridStorage.swift */; };
|
||||
D2CF986D1F694FFA00CE8F68 /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985D1F694FFA00CE8F68 /* MemoryStorage.swift */; };
|
||||
D2CF986E1F694FFA00CE8F68 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985E1F694FFA00CE8F68 /* Storage.swift */; };
|
||||
D2CF986F1F694FFA00CE8F68 /* StorageAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF985F1F694FFA00CE8F68 /* StorageAware.swift */; };
|
||||
D2CF987C1F69513800CE8F68 /* Date+ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98721F69513800CE8F68 /* Date+ExtensionsTests.swift */; };
|
||||
D2CF987D1F69513800CE8F68 /* MemoryCapsuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98741F69513800CE8F68 /* MemoryCapsuleTests.swift */; };
|
||||
D2CF987E1F69513800CE8F68 /* ExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98751F69513800CE8F68 /* ExpiryTests.swift */; };
|
||||
D2CF987F1F69513800CE8F68 /* ImageWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98761F69513800CE8F68 /* ImageWrapperTests.swift */; };
|
||||
D2CF98801F69513800CE8F68 /* TypeWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98771F69513800CE8F68 /* TypeWrapperTests.swift */; };
|
||||
D2CF98811F69513800CE8F68 /* DiskStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98791F69513800CE8F68 /* DiskStorageTests.swift */; };
|
||||
D2CF98821F69513800CE8F68 /* HybridStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF987A1F69513800CE8F68 /* HybridStorageTests.swift */; };
|
||||
D2CF98831F69513800CE8F68 /* MemoryStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF987B1F69513800CE8F68 /* MemoryStorageTests.swift */; };
|
||||
D2CF98851F69598900CE8F68 /* TypeWrapperStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98841F69598900CE8F68 /* TypeWrapperStorage.swift */; };
|
||||
D2CF98871F695B8F00CE8F68 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98861F695B8F00CE8F68 /* Types.swift */; };
|
||||
D2CF98891F695F9400CE8F68 /* TypeWrapperStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CF98881F695F9400CE8F68 /* TypeWrapperStorageTests.swift */; };
|
||||
D2D4CC161FA3145000E4A2D5 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4CC151FA3145000E4A2D5 /* MD5.swift */; };
|
||||
D2D4CC171FA3145000E4A2D5 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4CC151FA3145000E4A2D5 /* MD5.swift */; };
|
||||
D2D4CC181FA3145000E4A2D5 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4CC151FA3145000E4A2D5 /* MD5.swift */; };
|
||||
@@ -147,12 +162,23 @@
|
||||
/* Begin PBXFileReference section */
|
||||
BDEDD3561DBCE5B1007416A6 /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BDEDD3781DBCEB8A007416A6 /* Cache-tvOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Cache-tvOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D221E5BB20D00D9300BC940E /* MemoryStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryStorage.swift; sourceTree = "<group>"; };
|
||||
D221E5BF20D00DCC00BC940E /* Entry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = "<group>"; };
|
||||
D221E5C320D00DDB00BC940E /* DiskStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskStorage.swift; sourceTree = "<group>"; };
|
||||
D236F3191F6BEF73004EE01F /* StorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageTests.swift; sourceTree = "<group>"; };
|
||||
D270146F20D1018A003B45C7 /* Transformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformer.swift; sourceTree = "<group>"; };
|
||||
D270147320D101F3003B45C7 /* StorageAware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageAware.swift; sourceTree = "<group>"; };
|
||||
D270147720D1046A003B45C7 /* HybridStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HybridStorage.swift; sourceTree = "<group>"; };
|
||||
D270147B20D107DA003B45C7 /* SyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStorage.swift; sourceTree = "<group>"; };
|
||||
D270147F20D10982003B45C7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||
D270148320D10E76003B45C7 /* AsyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorage.swift; sourceTree = "<group>"; };
|
||||
D270148720D11040003B45C7 /* Storage+Transform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Transform.swift"; sourceTree = "<group>"; };
|
||||
D270148B20D113EA003B45C7 /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = "<group>"; };
|
||||
D270148F20D1251E003B45C7 /* TypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeWrapper.swift; sourceTree = "<group>"; };
|
||||
D270149320D125AC003B45C7 /* MemoryCapsule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCapsule.swift; sourceTree = "<group>"; };
|
||||
D27014A420D129EB003B45C7 /* TransformerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformerFactory.swift; sourceTree = "<group>"; };
|
||||
D285143E1F6FFE1F00C674D1 /* ObjectConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectConverterTests.swift; sourceTree = "<group>"; };
|
||||
D28897041F8B79B300C61DEE /* JSONDecoder+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D292DAF01F6A85F30060F614 /* SyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStorage.swift; sourceTree = "<group>"; };
|
||||
D292DAF41F6A94000060F614 /* AsyncStorageAware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorageAware.swift; sourceTree = "<group>"; };
|
||||
D292DAF81F6A962D0060F614 /* AsyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorage.swift; sourceTree = "<group>"; };
|
||||
D292DAFC1F6A970B0060F614 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
|
||||
D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStorageTests.swift; sourceTree = "<group>"; };
|
||||
D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorageTests.swift; sourceTree = "<group>"; };
|
||||
@@ -163,16 +189,10 @@
|
||||
D2CF984E1F694FFA00CE8F68 /* MemoryConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryConfig.swift; sourceTree = "<group>"; };
|
||||
D2CF98501F694FFA00CE8F68 /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D2CF98531F694FFA00CE8F68 /* DataSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSerializer.swift; sourceTree = "<group>"; };
|
||||
D2CF98541F694FFA00CE8F68 /* Entry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = "<group>"; };
|
||||
D2CF98551F694FFA00CE8F68 /* ExpirationMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpirationMode.swift; sourceTree = "<group>"; };
|
||||
D2CF98561F694FFA00CE8F68 /* Expiry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expiry.swift; sourceTree = "<group>"; };
|
||||
D2CF98571F694FFA00CE8F68 /* ImageWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageWrapper.swift; sourceTree = "<group>"; };
|
||||
D2CF98591F694FFA00CE8F68 /* StorageError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageError.swift; sourceTree = "<group>"; };
|
||||
D2CF985B1F694FFA00CE8F68 /* DiskStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskStorage.swift; sourceTree = "<group>"; };
|
||||
D2CF985C1F694FFA00CE8F68 /* HybridStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HybridStorage.swift; sourceTree = "<group>"; };
|
||||
D2CF985D1F694FFA00CE8F68 /* MemoryStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStorage.swift; sourceTree = "<group>"; };
|
||||
D2CF985E1F694FFA00CE8F68 /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||
D2CF985F1F694FFA00CE8F68 /* StorageAware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageAware.swift; sourceTree = "<group>"; };
|
||||
D2CF98721F69513800CE8F68 /* Date+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+ExtensionsTests.swift"; sourceTree = "<group>"; };
|
||||
D2CF98741F69513800CE8F68 /* MemoryCapsuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCapsuleTests.swift; sourceTree = "<group>"; };
|
||||
D2CF98751F69513800CE8F68 /* ExpiryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpiryTests.swift; sourceTree = "<group>"; };
|
||||
@@ -181,9 +201,8 @@
|
||||
D2CF98791F69513800CE8F68 /* DiskStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskStorageTests.swift; sourceTree = "<group>"; };
|
||||
D2CF987A1F69513800CE8F68 /* HybridStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HybridStorageTests.swift; sourceTree = "<group>"; };
|
||||
D2CF987B1F69513800CE8F68 /* MemoryStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStorageTests.swift; sourceTree = "<group>"; };
|
||||
D2CF98841F69598900CE8F68 /* TypeWrapperStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeWrapperStorage.swift; sourceTree = "<group>"; };
|
||||
D2CF98861F695B8F00CE8F68 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = "<group>"; };
|
||||
D2CF98881F695F9400CE8F68 /* TypeWrapperStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeWrapperStorageTests.swift; sourceTree = "<group>"; };
|
||||
D2CF98881F695F9400CE8F68 /* StorageSupportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSupportTests.swift; sourceTree = "<group>"; };
|
||||
D2D4CC151FA3145000E4A2D5 /* MD5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = "<group>"; };
|
||||
D2D4CC191FA3166900E4A2D5 /* MD5Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MD5Tests.swift; sourceTree = "<group>"; };
|
||||
D2D4CC1F1FA3411300E4A2D5 /* JSONDictionaryWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONDictionaryWrapper.swift; sourceTree = "<group>"; };
|
||||
@@ -297,7 +316,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2CF98531F694FFA00CE8F68 /* DataSerializer.swift */,
|
||||
D2CF98541F694FFA00CE8F68 /* Entry.swift */,
|
||||
D2CF98551F694FFA00CE8F68 /* ExpirationMode.swift */,
|
||||
D2CF98561F694FFA00CE8F68 /* Expiry.swift */,
|
||||
D2CF98571F694FFA00CE8F68 /* ImageWrapper.swift */,
|
||||
@@ -307,6 +325,12 @@
|
||||
D2D4CC151FA3145000E4A2D5 /* MD5.swift */,
|
||||
D2D4CC1F1FA3411300E4A2D5 /* JSONDictionaryWrapper.swift */,
|
||||
D2D4CC231FA3426B00E4A2D5 /* JSONArrayWrapper.swift */,
|
||||
D221E5BF20D00DCC00BC940E /* Entry.swift */,
|
||||
D270146F20D1018A003B45C7 /* Transformer.swift */,
|
||||
D270148B20D113EA003B45C7 /* Optional+Extension.swift */,
|
||||
D270148F20D1251E003B45C7 /* TypeWrapper.swift */,
|
||||
D270149320D125AC003B45C7 /* MemoryCapsule.swift */,
|
||||
D27014A420D129EB003B45C7 /* TransformerFactory.swift */,
|
||||
);
|
||||
path = Library;
|
||||
sourceTree = "<group>";
|
||||
@@ -314,15 +338,14 @@
|
||||
D2CF985A1F694FFA00CE8F68 /* Storage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2CF985B1F694FFA00CE8F68 /* DiskStorage.swift */,
|
||||
D2CF985C1F694FFA00CE8F68 /* HybridStorage.swift */,
|
||||
D2CF985D1F694FFA00CE8F68 /* MemoryStorage.swift */,
|
||||
D2CF985E1F694FFA00CE8F68 /* Storage.swift */,
|
||||
D2CF985F1F694FFA00CE8F68 /* StorageAware.swift */,
|
||||
D2CF98841F69598900CE8F68 /* TypeWrapperStorage.swift */,
|
||||
D292DAF01F6A85F30060F614 /* SyncStorage.swift */,
|
||||
D292DAF41F6A94000060F614 /* AsyncStorageAware.swift */,
|
||||
D292DAF81F6A962D0060F614 /* AsyncStorage.swift */,
|
||||
D221E5BB20D00D9300BC940E /* MemoryStorage.swift */,
|
||||
D221E5C320D00DDB00BC940E /* DiskStorage.swift */,
|
||||
D270147320D101F3003B45C7 /* StorageAware.swift */,
|
||||
D270147720D1046A003B45C7 /* HybridStorage.swift */,
|
||||
D270147B20D107DA003B45C7 /* SyncStorage.swift */,
|
||||
D270147F20D10982003B45C7 /* Storage.swift */,
|
||||
D270148320D10E76003B45C7 /* AsyncStorage.swift */,
|
||||
D270148720D11040003B45C7 /* Storage+Transform.swift */,
|
||||
);
|
||||
path = Storage;
|
||||
sourceTree = "<group>";
|
||||
@@ -365,7 +388,7 @@
|
||||
D2CF98791F69513800CE8F68 /* DiskStorageTests.swift */,
|
||||
D2CF987A1F69513800CE8F68 /* HybridStorageTests.swift */,
|
||||
D2CF987B1F69513800CE8F68 /* MemoryStorageTests.swift */,
|
||||
D2CF98881F695F9400CE8F68 /* TypeWrapperStorageTests.swift */,
|
||||
D2CF98881F695F9400CE8F68 /* StorageSupportTests.swift */,
|
||||
D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */,
|
||||
D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */,
|
||||
D236F3191F6BEF73004EE01F /* StorageTests.swift */,
|
||||
@@ -767,31 +790,35 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D21B66861F6A723C00125DE1 /* Entry.swift in Sources */,
|
||||
D21B66971F6A724000125DE1 /* StorageAware.swift in Sources */,
|
||||
D221E5C620D00DDB00BC940E /* DiskStorage.swift in Sources */,
|
||||
D21B669D1F6A724600125DE1 /* DiskConfig.swift in Sources */,
|
||||
D21B66961F6A724000125DE1 /* Storage.swift in Sources */,
|
||||
D21B66871F6A723C00125DE1 /* ExpirationMode.swift in Sources */,
|
||||
D21B66881F6A723C00125DE1 /* Expiry.swift in Sources */,
|
||||
D270147620D101F3003B45C7 /* StorageAware.swift in Sources */,
|
||||
D21B669E1F6A724700125DE1 /* MemoryConfig.swift in Sources */,
|
||||
D270149220D1251E003B45C7 /* TypeWrapper.swift in Sources */,
|
||||
D2D4CC221FA3411300E4A2D5 /* JSONDictionaryWrapper.swift in Sources */,
|
||||
D2D4CC181FA3145000E4A2D5 /* MD5.swift in Sources */,
|
||||
D21B66951F6A724000125DE1 /* MemoryStorage.swift in Sources */,
|
||||
D292DAF31F6A85F30060F614 /* SyncStorage.swift in Sources */,
|
||||
D221E5BE20D00D9300BC940E /* MemoryStorage.swift in Sources */,
|
||||
D270148E20D113EA003B45C7 /* Optional+Extension.swift in Sources */,
|
||||
D270147220D1018A003B45C7 /* Transformer.swift in Sources */,
|
||||
D21B668C1F6A723C00125DE1 /* Types.swift in Sources */,
|
||||
D2D4CC261FA3426B00E4A2D5 /* JSONArrayWrapper.swift in Sources */,
|
||||
D27014A720D129EB003B45C7 /* TransformerFactory.swift in Sources */,
|
||||
D270147E20D107DA003B45C7 /* SyncStorage.swift in Sources */,
|
||||
D292DAFF1F6A970B0060F614 /* Result.swift in Sources */,
|
||||
D21B669A1F6A724300125DE1 /* Date+Extensions.swift in Sources */,
|
||||
D21B66891F6A723C00125DE1 /* ImageWrapper.swift in Sources */,
|
||||
D21B668B1F6A723C00125DE1 /* StorageError.swift in Sources */,
|
||||
D292DAFB1F6A962D0060F614 /* AsyncStorage.swift in Sources */,
|
||||
D270148A20D11040003B45C7 /* Storage+Transform.swift in Sources */,
|
||||
D5A138C21EB29BFA00881A20 /* UIImage+Extensions.swift in Sources */,
|
||||
D21B66851F6A723C00125DE1 /* DataSerializer.swift in Sources */,
|
||||
D21B66941F6A724000125DE1 /* HybridStorage.swift in Sources */,
|
||||
D270147A20D1046A003B45C7 /* HybridStorage.swift in Sources */,
|
||||
D270148620D10E76003B45C7 /* AsyncStorage.swift in Sources */,
|
||||
D270149620D125AC003B45C7 /* MemoryCapsule.swift in Sources */,
|
||||
D28897071F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
|
||||
D292DAF71F6A94000060F614 /* AsyncStorageAware.swift in Sources */,
|
||||
D21B66931F6A724000125DE1 /* DiskStorage.swift in Sources */,
|
||||
D21B66981F6A724000125DE1 /* TypeWrapperStorage.swift in Sources */,
|
||||
D270148220D10982003B45C7 /* Storage.swift in Sources */,
|
||||
D221E5C220D00DCC00BC940E /* Entry.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -801,8 +828,14 @@
|
||||
files = (
|
||||
D2CF98201F69427C00CE8F68 /* TestCase+Extensions.swift in Sources */,
|
||||
D2CF98231F69427C00CE8F68 /* TestHelper.swift in Sources */,
|
||||
D27014A320D129A3003B45C7 /* DiskStorageTests.swift in Sources */,
|
||||
D27014A020D12870003B45C7 /* MemoryStorageTests.swift in Sources */,
|
||||
D2CF98261F69427C00CE8F68 /* User.swift in Sources */,
|
||||
D27014AE20D12D83003B45C7 /* AsyncStorageTests.swift in Sources */,
|
||||
D28A1D241F6FFEF60030DF81 /* ObjectConverterTests.swift in Sources */,
|
||||
D27014B120D12E38003B45C7 /* StorageSupportTests.swift in Sources */,
|
||||
D27014AD20D12CC3003B45C7 /* SyncStorageTests.swift in Sources */,
|
||||
D27014AA20D12BA4003B45C7 /* HybridStorageTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -812,24 +845,23 @@
|
||||
files = (
|
||||
D2CF987E1F69513800CE8F68 /* ExpiryTests.swift in Sources */,
|
||||
D28A1D231F6FFEF50030DF81 /* ObjectConverterTests.swift in Sources */,
|
||||
D2CF98891F695F9400CE8F68 /* TypeWrapperStorageTests.swift in Sources */,
|
||||
D2CF98821F69513800CE8F68 /* HybridStorageTests.swift in Sources */,
|
||||
D2CF98831F69513800CE8F68 /* MemoryStorageTests.swift in Sources */,
|
||||
D270149E20D1286F003B45C7 /* MemoryStorageTests.swift in Sources */,
|
||||
D2CF987C1F69513800CE8F68 /* Date+ExtensionsTests.swift in Sources */,
|
||||
D28C9BAC1F67ECD400C180C1 /* TestHelper+iOS.swift in Sources */,
|
||||
D2CF98211F69427C00CE8F68 /* TestHelper.swift in Sources */,
|
||||
D2CF987F1F69513800CE8F68 /* ImageWrapperTests.swift in Sources */,
|
||||
D2D4CC1A1FA3166900E4A2D5 /* MD5Tests.swift in Sources */,
|
||||
D292DB041F6AA0730060F614 /* AsyncStorageTests.swift in Sources */,
|
||||
D2D4CC281FA342CA00E4A2D5 /* JSONWrapperTests.swift in Sources */,
|
||||
D28C9BAF1F67EF8300C180C1 /* UIImage+ExtensionsTests.swift in Sources */,
|
||||
D2CF987D1F69513800CE8F68 /* MemoryCapsuleTests.swift in Sources */,
|
||||
D27014A120D129A2003B45C7 /* DiskStorageTests.swift in Sources */,
|
||||
D27014B020D12E37003B45C7 /* StorageSupportTests.swift in Sources */,
|
||||
D2CF98801F69513800CE8F68 /* TypeWrapperTests.swift in Sources */,
|
||||
D27014A820D12BA3003B45C7 /* HybridStorageTests.swift in Sources */,
|
||||
D27014AF20D12D84003B45C7 /* AsyncStorageTests.swift in Sources */,
|
||||
D2CF98241F69427C00CE8F68 /* User.swift in Sources */,
|
||||
D27014AC20D12CC1003B45C7 /* SyncStorageTests.swift in Sources */,
|
||||
D2CF981E1F69427C00CE8F68 /* TestCase+Extensions.swift in Sources */,
|
||||
D236F31A1F6BEF73004EE01F /* StorageTests.swift in Sources */,
|
||||
D292DB011F6AA06B0060F614 /* SyncStorageTests.swift in Sources */,
|
||||
D2CF98811F69513800CE8F68 /* DiskStorageTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -837,31 +869,35 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D21B667D1F6A723C00125DE1 /* Entry.swift in Sources */,
|
||||
D21B66911F6A723F00125DE1 /* StorageAware.swift in Sources */,
|
||||
D221E5C520D00DDB00BC940E /* DiskStorage.swift in Sources */,
|
||||
D21B669B1F6A724600125DE1 /* DiskConfig.swift in Sources */,
|
||||
D21B66901F6A723F00125DE1 /* Storage.swift in Sources */,
|
||||
D21B667E1F6A723C00125DE1 /* ExpirationMode.swift in Sources */,
|
||||
D21B667F1F6A723C00125DE1 /* Expiry.swift in Sources */,
|
||||
D270147520D101F3003B45C7 /* StorageAware.swift in Sources */,
|
||||
D21B669C1F6A724600125DE1 /* MemoryConfig.swift in Sources */,
|
||||
D270149120D1251E003B45C7 /* TypeWrapper.swift in Sources */,
|
||||
D2D4CC211FA3411300E4A2D5 /* JSONDictionaryWrapper.swift in Sources */,
|
||||
D2D4CC171FA3145000E4A2D5 /* MD5.swift in Sources */,
|
||||
D21B668F1F6A723F00125DE1 /* MemoryStorage.swift in Sources */,
|
||||
D292DAF21F6A85F30060F614 /* SyncStorage.swift in Sources */,
|
||||
D221E5BD20D00D9300BC940E /* MemoryStorage.swift in Sources */,
|
||||
D270148D20D113EA003B45C7 /* Optional+Extension.swift in Sources */,
|
||||
D270147120D1018A003B45C7 /* Transformer.swift in Sources */,
|
||||
D21B66831F6A723C00125DE1 /* Types.swift in Sources */,
|
||||
D2D4CC251FA3426B00E4A2D5 /* JSONArrayWrapper.swift in Sources */,
|
||||
D27014A620D129EB003B45C7 /* TransformerFactory.swift in Sources */,
|
||||
D270147D20D107DA003B45C7 /* SyncStorage.swift in Sources */,
|
||||
D292DAFE1F6A970B0060F614 /* Result.swift in Sources */,
|
||||
D21B66991F6A724200125DE1 /* Date+Extensions.swift in Sources */,
|
||||
D21B66801F6A723C00125DE1 /* ImageWrapper.swift in Sources */,
|
||||
D21B66821F6A723C00125DE1 /* StorageError.swift in Sources */,
|
||||
D292DAFA1F6A962D0060F614 /* AsyncStorage.swift in Sources */,
|
||||
D270148920D11040003B45C7 /* Storage+Transform.swift in Sources */,
|
||||
D5A138C41EB29C2100881A20 /* NSImage+Extensions.swift in Sources */,
|
||||
D21B667C1F6A723C00125DE1 /* DataSerializer.swift in Sources */,
|
||||
D21B668E1F6A723F00125DE1 /* HybridStorage.swift in Sources */,
|
||||
D270147920D1046A003B45C7 /* HybridStorage.swift in Sources */,
|
||||
D270148520D10E76003B45C7 /* AsyncStorage.swift in Sources */,
|
||||
D270149520D125AC003B45C7 /* MemoryCapsule.swift in Sources */,
|
||||
D28897061F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
|
||||
D292DAF61F6A94000060F614 /* AsyncStorageAware.swift in Sources */,
|
||||
D21B668D1F6A723F00125DE1 /* DiskStorage.swift in Sources */,
|
||||
D21B66921F6A723F00125DE1 /* TypeWrapperStorage.swift in Sources */,
|
||||
D270148120D10982003B45C7 /* Storage.swift in Sources */,
|
||||
D221E5C120D00DCC00BC940E /* Entry.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -869,7 +905,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D292DB051F6AA0730060F614 /* AsyncStorageTests.swift in Sources */,
|
||||
D28A1D221F6FFEF50030DF81 /* ObjectConverterTests.swift in Sources */,
|
||||
D2CF98221F69427C00CE8F68 /* TestHelper.swift in Sources */,
|
||||
D21B66A11F6A747000125DE1 /* ImageWrapperTests.swift in Sources */,
|
||||
@@ -877,7 +912,6 @@
|
||||
D5291D851C283C7C00B702C9 /* TestHelper+OSX.swift in Sources */,
|
||||
D5291DA31C2841D200B702C9 /* NSImage+ExtensionsTests.swift in Sources */,
|
||||
D2CF981F1F69427C00CE8F68 /* TestCase+Extensions.swift in Sources */,
|
||||
D292DB021F6AA06B0060F614 /* SyncStorageTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -885,31 +919,35 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D2CF98651F694FFA00CE8F68 /* Entry.swift in Sources */,
|
||||
D221E5C420D00DDB00BC940E /* DiskStorage.swift in Sources */,
|
||||
D2CF98681F694FFA00CE8F68 /* ImageWrapper.swift in Sources */,
|
||||
D2CF986C1F694FFA00CE8F68 /* HybridStorage.swift in Sources */,
|
||||
D2CF98871F695B8F00CE8F68 /* Types.swift in Sources */,
|
||||
D2CF98621F694FFA00CE8F68 /* Date+Extensions.swift in Sources */,
|
||||
D2CF98641F694FFA00CE8F68 /* DataSerializer.swift in Sources */,
|
||||
D2CF986B1F694FFA00CE8F68 /* DiskStorage.swift in Sources */,
|
||||
D270147420D101F3003B45C7 /* StorageAware.swift in Sources */,
|
||||
D270149020D1251E003B45C7 /* TypeWrapper.swift in Sources */,
|
||||
D2D4CC201FA3411300E4A2D5 /* JSONDictionaryWrapper.swift in Sources */,
|
||||
D2D4CC161FA3145000E4A2D5 /* MD5.swift in Sources */,
|
||||
D292DAF11F6A85F30060F614 /* SyncStorage.swift in Sources */,
|
||||
D221E5BC20D00D9300BC940E /* MemoryStorage.swift in Sources */,
|
||||
D270148C20D113EA003B45C7 /* Optional+Extension.swift in Sources */,
|
||||
D2CF98601F694FFA00CE8F68 /* DiskConfig.swift in Sources */,
|
||||
D270147020D1018A003B45C7 /* Transformer.swift in Sources */,
|
||||
D292DAFD1F6A970B0060F614 /* Result.swift in Sources */,
|
||||
D27014A520D129EB003B45C7 /* TransformerFactory.swift in Sources */,
|
||||
D2D4CC241FA3426B00E4A2D5 /* JSONArrayWrapper.swift in Sources */,
|
||||
D2CF986D1F694FFA00CE8F68 /* MemoryStorage.swift in Sources */,
|
||||
D2CF986F1F694FFA00CE8F68 /* StorageAware.swift in Sources */,
|
||||
D2CF98851F69598900CE8F68 /* TypeWrapperStorage.swift in Sources */,
|
||||
D270147C20D107DA003B45C7 /* SyncStorage.swift in Sources */,
|
||||
D2CF98671F694FFA00CE8F68 /* Expiry.swift in Sources */,
|
||||
D292DAF91F6A962D0060F614 /* AsyncStorage.swift in Sources */,
|
||||
D270148820D11040003B45C7 /* Storage+Transform.swift in Sources */,
|
||||
D2CF986A1F694FFA00CE8F68 /* StorageError.swift in Sources */,
|
||||
D5A138C11EB29BFA00881A20 /* UIImage+Extensions.swift in Sources */,
|
||||
D2CF986E1F694FFA00CE8F68 /* Storage.swift in Sources */,
|
||||
D270147820D1046A003B45C7 /* HybridStorage.swift in Sources */,
|
||||
D270148420D10E76003B45C7 /* AsyncStorage.swift in Sources */,
|
||||
D28897051F8B79B300C61DEE /* JSONDecoder+Extensions.swift in Sources */,
|
||||
D292DAF51F6A94000060F614 /* AsyncStorageAware.swift in Sources */,
|
||||
D270148020D10982003B45C7 /* Storage.swift in Sources */,
|
||||
D270149420D125AC003B45C7 /* MemoryCapsule.swift in Sources */,
|
||||
D2CF98611F694FFA00CE8F68 /* MemoryConfig.swift in Sources */,
|
||||
D2CF98661F694FFA00CE8F68 /* ExpirationMode.swift in Sources */,
|
||||
D221E5C020D00DCC00BC940E /* Entry.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -4,19 +4,17 @@ public struct MemoryConfig {
|
||||
/// Expiry date that will be applied by default for every added object
|
||||
/// if it's not overridden in the add(key: object: expiry: completion:) method
|
||||
public let expiry: Expiry
|
||||
/// The maximum number of objects in memory the cache should hold. 0 means no limit.
|
||||
/// The maximum number of objects in memory the cache should hold.
|
||||
/// If 0, there is no count limit. The default value is 0.
|
||||
public let countLimit: UInt
|
||||
|
||||
public init(expiry: Expiry = .never, countLimit: UInt = 0) {
|
||||
/// The maximum total cost that the cache can hold before it starts evicting objects.
|
||||
/// If 0, there is no total cost limit. The default value is 0
|
||||
public let totalCostLimit: UInt
|
||||
|
||||
public init(expiry: Expiry = .never, countLimit: UInt = 0, totalCostLimit: UInt = 0) {
|
||||
self.expiry = expiry
|
||||
self.countLimit = countLimit
|
||||
}
|
||||
|
||||
// MARK: - Deprecated
|
||||
@available(*, deprecated,
|
||||
message: "Use init(expiry:countLimit:) instead.",
|
||||
renamed: "init(expiry:countLimit:)")
|
||||
public init(expiry: Expiry = .never, countLimit: UInt = 0, totalCostLimit: UInt = 0) {
|
||||
self.init(expiry: expiry, countLimit: countLimit)
|
||||
self.totalCostLimit = totalCostLimit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
/// A wrapper around cached object and its expiry date.
|
||||
public struct Entry<T: Codable> {
|
||||
public struct Entry<T> {
|
||||
/// Cached object
|
||||
public let object: T
|
||||
/// Expiry date
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
|
||||
/// Helper class to hold cached instance and expiry date.
|
||||
/// Used in memory storage to work with NSCache.
|
||||
class MemoryCapsule: NSObject {
|
||||
/// Object to be cached
|
||||
let object: Any
|
||||
/// Expiration date
|
||||
let expiry: Expiry
|
||||
|
||||
/**
|
||||
Creates a new instance of Capsule.
|
||||
- Parameter value: Object to be cached
|
||||
- Parameter expiry: Expiration date
|
||||
*/
|
||||
init(value: Any, expiry: Expiry) {
|
||||
self.object = value
|
||||
self.expiry = expiry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public extension Optional {
|
||||
func unwrapOrThrow(error: Error) throws -> Wrapped {
|
||||
if let value = self {
|
||||
return value
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,6 @@ public enum StorageError: Error {
|
||||
case encodingFailed
|
||||
/// The storage has been deallocated
|
||||
case deallocated
|
||||
/// Fail to perform transformation to or from Data
|
||||
case transformerFail
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public class Transformer<T> {
|
||||
let toData: (T) throws -> Data
|
||||
let fromData: (Data) throws -> T
|
||||
|
||||
public init(toData: @escaping (T) throws -> Data, fromData: @escaping (Data) throws -> T) {
|
||||
self.toData = toData
|
||||
self.fromData = fromData
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import Foundation
|
||||
|
||||
public class TransformerFactory {
|
||||
public static func forData() -> Transformer<Data> {
|
||||
let toData: (Data) throws -> Data = { $0 }
|
||||
|
||||
let fromData: (Data) throws -> Data = { $0 }
|
||||
|
||||
return Transformer<Data>(toData: toData, fromData: fromData)
|
||||
}
|
||||
|
||||
public static func forImage() -> Transformer<Image> {
|
||||
let toData: (Image) throws -> Data = { image in
|
||||
return try image.cache_toData().unwrapOrThrow(error: StorageError.transformerFail)
|
||||
}
|
||||
|
||||
let fromData: (Data) throws -> Image = { data in
|
||||
return try Image(data: data).unwrapOrThrow(error: StorageError.transformerFail)
|
||||
}
|
||||
|
||||
return Transformer<Image>(toData: toData, fromData: fromData)
|
||||
}
|
||||
|
||||
public static func forCodable<U: Codable>(ofType: U.Type) -> Transformer<U> {
|
||||
let toData: (U) throws -> Data = { object in
|
||||
let wrapper = TypeWrapper<U>(object: object)
|
||||
let encoder = JSONEncoder()
|
||||
return try encoder.encode(wrapper)
|
||||
}
|
||||
|
||||
let fromData: (Data) throws -> U = { data in
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(TypeWrapper<U>.self, from: data).object
|
||||
}
|
||||
|
||||
return Transformer<U>(toData: toData, fromData: fromData)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
/// Used to wrap Codable object
|
||||
public struct TypeWrapper<T: Codable>: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case object
|
||||
}
|
||||
|
||||
public let object: T
|
||||
|
||||
public init(object: T) {
|
||||
self.object = object
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,18 @@ import Dispatch
|
||||
|
||||
/// Manipulate storage in a "all async" manner.
|
||||
/// The completion closure will be called when operation completes.
|
||||
public class AsyncStorage {
|
||||
fileprivate let internalStorage: StorageAware
|
||||
public class AsyncStorage<T> {
|
||||
public let innerStorage: HybridStorage<T>
|
||||
public let serialQueue: DispatchQueue
|
||||
|
||||
init(storage: StorageAware, serialQueue: DispatchQueue) {
|
||||
self.internalStorage = storage
|
||||
public init(storage: HybridStorage<T>, serialQueue: DispatchQueue) {
|
||||
self.innerStorage = storage
|
||||
self.serialQueue = serialQueue
|
||||
}
|
||||
}
|
||||
|
||||
extension AsyncStorage: AsyncStorageAware {
|
||||
public func entry<T>(ofType type: T.Type, forKey key: String, completion: @escaping (Result<Entry<T>>) -> Void) {
|
||||
extension AsyncStorage {
|
||||
public func entry(forKey key: String, completion: @escaping (Result<Entry<T>>) -> Void) {
|
||||
serialQueue.async { [weak self] in
|
||||
guard let `self` = self else {
|
||||
completion(Result.error(StorageError.deallocated))
|
||||
@@ -22,7 +22,7 @@ extension AsyncStorage: AsyncStorageAware {
|
||||
}
|
||||
|
||||
do {
|
||||
let anEntry = try self.internalStorage.entry(ofType: type, forKey: key)
|
||||
let anEntry = try self.innerStorage.entry(forKey: key)
|
||||
completion(Result.value(anEntry))
|
||||
} catch {
|
||||
completion(Result.error(error))
|
||||
@@ -38,7 +38,7 @@ extension AsyncStorage: AsyncStorageAware {
|
||||
}
|
||||
|
||||
do {
|
||||
try self.internalStorage.removeObject(forKey: key)
|
||||
try self.innerStorage.removeObject(forKey: key)
|
||||
completion(Result.value(()))
|
||||
} catch {
|
||||
completion(Result.error(error))
|
||||
@@ -46,10 +46,11 @@ extension AsyncStorage: AsyncStorageAware {
|
||||
}
|
||||
}
|
||||
|
||||
public func setObject<T: Codable>(_ object: T,
|
||||
forKey key: String,
|
||||
expiry: Expiry? = nil,
|
||||
completion: @escaping (Result<()>) -> Void) {
|
||||
public func setObject(
|
||||
_ object: T,
|
||||
forKey key: String,
|
||||
expiry: Expiry? = nil,
|
||||
completion: @escaping (Result<()>) -> Void) {
|
||||
serialQueue.async { [weak self] in
|
||||
guard let `self` = self else {
|
||||
completion(Result.error(StorageError.deallocated))
|
||||
@@ -57,7 +58,7 @@ extension AsyncStorage: AsyncStorageAware {
|
||||
}
|
||||
|
||||
do {
|
||||
try self.internalStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
try self.innerStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
completion(Result.value(()))
|
||||
} catch {
|
||||
completion(Result.error(error))
|
||||
@@ -73,7 +74,7 @@ extension AsyncStorage: AsyncStorageAware {
|
||||
}
|
||||
|
||||
do {
|
||||
try self.internalStorage.removeAll()
|
||||
try self.innerStorage.removeAll()
|
||||
completion(Result.value(()))
|
||||
} catch {
|
||||
completion(Result.error(error))
|
||||
@@ -89,11 +90,40 @@ extension AsyncStorage: AsyncStorageAware {
|
||||
}
|
||||
|
||||
do {
|
||||
try self.internalStorage.removeExpiredObjects()
|
||||
try self.innerStorage.removeExpiredObjects()
|
||||
completion(Result.value(()))
|
||||
} catch {
|
||||
completion(Result.error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func object(forKey key: String, completion: @escaping (Result<T>) -> Void) {
|
||||
entry(forKey: key, completion: { (result: Result<Entry<T>>) in
|
||||
completion(result.map({ entry in
|
||||
return entry.object
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
public func existsObject(
|
||||
forKey key: String,
|
||||
completion: @escaping (Result<Bool>) -> Void) {
|
||||
object(forKey: key, completion: { (result: Result<T>) in
|
||||
completion(result.map({ _ in
|
||||
return true
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public extension AsyncStorage {
|
||||
func transform<U>(transformer: Transformer<U>) -> AsyncStorage<U> {
|
||||
let storage = AsyncStorage<U>(
|
||||
storage: innerStorage.transform(transformer: transformer),
|
||||
serialQueue: serialQueue
|
||||
)
|
||||
|
||||
return storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import Foundation
|
||||
import Dispatch
|
||||
|
||||
/// A protocol used for saving and loading from storage in async manner.
|
||||
public protocol AsyncStorageAware: class {
|
||||
/// All async operation must act on a serial queue.
|
||||
var serialQueue: DispatchQueue { get }
|
||||
|
||||
/**
|
||||
Tries to retrieve the object from the storage.
|
||||
- Parameter key: Unique key to identify the object in the cache.
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func object<T: Codable>(ofType type: T.Type, forKey key: String, completion: @escaping (Result<T>) -> Void)
|
||||
|
||||
/**
|
||||
Get cache entry which includes object with metadata.
|
||||
- Parameter key: Unique key to identify the object in the cache
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func entry<T>(ofType type: T.Type, forKey key: String, completion: @escaping (Result<Entry<T>>) -> Void)
|
||||
|
||||
/**
|
||||
Removes the object by the given key.
|
||||
- Parameter key: Unique key to identify the object.
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func removeObject(forKey key: String, completion: @escaping (Result<()>) -> Void)
|
||||
|
||||
/**
|
||||
Saves passed object.
|
||||
- Parameter key: Unique key to identify the object in the cache.
|
||||
- Parameter object: Object that needs to be cached.
|
||||
- Parameter expiry: Overwrite expiry for this object only.
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func setObject<T: Codable>(_ object: T,
|
||||
forKey key: String,
|
||||
expiry: Expiry?,
|
||||
completion: @escaping (Result<()>) -> Void)
|
||||
|
||||
/**
|
||||
Check if an object exist by the given key.
|
||||
- Parameter key: Unique key to identify the object.
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func existsObject<T: Codable>(ofType type: T.Type,
|
||||
forKey key: String,
|
||||
completion: @escaping (Result<Bool>) -> Void)
|
||||
|
||||
/**
|
||||
Removes all objects from the cache storage.
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func removeAll(completion: @escaping (Result<()>) -> Void)
|
||||
|
||||
/**
|
||||
Clears all expired objects.
|
||||
- Parameter completion: Triggered until the operation completes.
|
||||
*/
|
||||
func removeExpiredObjects(completion: @escaping (Result<()>) -> Void)
|
||||
}
|
||||
|
||||
public extension AsyncStorageAware {
|
||||
func object<T: Codable>(ofType type: T.Type, forKey key: String, completion: @escaping (Result<T>) -> Void) {
|
||||
entry(ofType: type, forKey: key, completion: { (result: Result<Entry<T>>) in
|
||||
completion(result.map({ entry in
|
||||
return entry.object
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func existsObject<T: Codable>(ofType type: T.Type,
|
||||
forKey key: String,
|
||||
completion: @escaping (Result<Bool>) -> Void) {
|
||||
object(ofType: type, forKey: key, completion: { (result: Result<T>) in
|
||||
completion(result.map({ _ in
|
||||
return true
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,61 @@
|
||||
import Foundation
|
||||
|
||||
/// Save objects to file on disk
|
||||
final class DiskStorage {
|
||||
final public class DiskStorage<T> {
|
||||
enum Error: Swift.Error {
|
||||
case fileEnumeratorFailed
|
||||
}
|
||||
|
||||
/// File manager to read/write to the disk
|
||||
fileprivate let fileManager: FileManager
|
||||
public let fileManager: FileManager
|
||||
/// Configuration
|
||||
fileprivate let config: DiskConfig
|
||||
/// The computed path `directory+name`
|
||||
let path: String
|
||||
public let path: String
|
||||
|
||||
private let transformer: Transformer<T>
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
required init(config: DiskConfig, fileManager: FileManager = FileManager.default) throws {
|
||||
public required init(config: DiskConfig, fileManager: FileManager = FileManager.default, transformer: Transformer<T>) throws {
|
||||
self.config = config
|
||||
self.fileManager = fileManager
|
||||
self.transformer = transformer
|
||||
|
||||
let url: URL
|
||||
if let directory = config.directory {
|
||||
url = directory
|
||||
} else {
|
||||
url = try fileManager.url(
|
||||
for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true
|
||||
for: .documentDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: true
|
||||
)
|
||||
}
|
||||
|
||||
// path
|
||||
path = url.appendingPathComponent(config.name, isDirectory: true).path
|
||||
self.path = url.appendingPathComponent(config.name, isDirectory: true).path
|
||||
|
||||
try createDirectory()
|
||||
|
||||
// protection
|
||||
#if os(iOS) || os(tvOS)
|
||||
if let protectionType = config.protectionType {
|
||||
try setDirectoryAttributes([
|
||||
FileAttributeKey.protectionKey: protectionType
|
||||
])
|
||||
}
|
||||
if let protectionType = config.protectionType {
|
||||
try setDirectoryAttributes([
|
||||
FileAttributeKey.protectionKey: protectionType
|
||||
])
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extension DiskStorage: StorageAware {
|
||||
func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T> {
|
||||
public func entry(forKey key: String) throws -> Entry<T> {
|
||||
let filePath = makeFilePath(for: key)
|
||||
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
|
||||
let attributes = try fileManager.attributesOfItem(atPath: filePath)
|
||||
let object: T = try DataSerializer.deserialize(data: data)
|
||||
let object = try transformer.fromData(data)
|
||||
|
||||
guard let date = attributes[.modificationDate] as? Date else {
|
||||
throw StorageError.malformedFileAttributes
|
||||
@@ -65,24 +72,24 @@ extension DiskStorage: StorageAware {
|
||||
)
|
||||
}
|
||||
|
||||
func setObject<T: Codable>(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
|
||||
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
|
||||
let expiry = expiry ?? config.expiry
|
||||
let data = try DataSerializer.serialize(object: object)
|
||||
let data = try transformer.toData(object)
|
||||
let filePath = makeFilePath(for: key)
|
||||
_ = fileManager.createFile(atPath: filePath, contents: data, attributes: nil)
|
||||
try fileManager.setAttributes([.modificationDate: expiry.date], ofItemAtPath: filePath)
|
||||
}
|
||||
|
||||
func removeObject(forKey key: String) throws {
|
||||
public func removeObject(forKey key: String) throws {
|
||||
try fileManager.removeItem(atPath: makeFilePath(for: key))
|
||||
}
|
||||
|
||||
func removeAll() throws {
|
||||
public func removeAll() throws {
|
||||
try fileManager.removeItem(atPath: path)
|
||||
try createDirectory()
|
||||
}
|
||||
|
||||
func removeExpiredObjects() throws {
|
||||
public func removeExpiredObjects() throws {
|
||||
let storageURL = URL(fileURLWithPath: path)
|
||||
let resourceKeys: [URLResourceKey] = [
|
||||
.isDirectoryKey,
|
||||
@@ -229,3 +236,16 @@ extension DiskStorage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension DiskStorage {
|
||||
func transform<U>(transformer: Transformer<U>) -> DiskStorage<U> {
|
||||
// swiftlint:disable force_try
|
||||
let storage = try! DiskStorage<U>(
|
||||
config: config,
|
||||
fileManager: fileManager,
|
||||
transformer: transformer
|
||||
)
|
||||
|
||||
return storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,56 @@
|
||||
import Foundation
|
||||
|
||||
/// Use both memory and disk storage. Try on memory first.
|
||||
class HybridStorage {
|
||||
let memoryStorage: MemoryStorage
|
||||
let diskStorage: DiskStorage
|
||||
public class HybridStorage<T> {
|
||||
public let memoryStorage: MemoryStorage<T>
|
||||
public let diskStorage: DiskStorage<T>
|
||||
|
||||
init(memoryStorage: MemoryStorage, diskStorage: DiskStorage) {
|
||||
public init(memoryStorage: MemoryStorage<T>, diskStorage: DiskStorage<T>) {
|
||||
self.memoryStorage = memoryStorage
|
||||
self.diskStorage = diskStorage
|
||||
}
|
||||
}
|
||||
|
||||
extension HybridStorage: StorageAware {
|
||||
func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T> {
|
||||
public func entry(forKey key: String) throws -> Entry<T> {
|
||||
do {
|
||||
return try memoryStorage.entry(ofType: type, forKey: key)
|
||||
return try memoryStorage.entry(forKey: key)
|
||||
} catch {
|
||||
let entry = try diskStorage.entry(ofType: type, forKey: key)
|
||||
let entry = try diskStorage.entry(forKey: key)
|
||||
// set back to memoryStorage
|
||||
memoryStorage.setObject(entry.object, forKey: key, expiry: entry.expiry)
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
||||
func removeObject(forKey key: String) throws {
|
||||
public func removeObject(forKey key: String) throws {
|
||||
memoryStorage.removeObject(forKey: key)
|
||||
try diskStorage.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
func setObject<T: Codable>(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
|
||||
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
|
||||
memoryStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
try diskStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
}
|
||||
|
||||
func removeAll() throws {
|
||||
public func removeAll() throws {
|
||||
memoryStorage.removeAll()
|
||||
try diskStorage.removeAll()
|
||||
}
|
||||
|
||||
func removeExpiredObjects() throws {
|
||||
public func removeExpiredObjects() throws {
|
||||
memoryStorage.removeExpiredObjects()
|
||||
try diskStorage.removeExpiredObjects()
|
||||
}
|
||||
}
|
||||
|
||||
public extension HybridStorage {
|
||||
func transform<U>(transformer: Transformer<U>) -> HybridStorage<U> {
|
||||
let storage = HybridStorage<U>(
|
||||
memoryStorage: memoryStorage.transform(),
|
||||
diskStorage: diskStorage.transform(transformer: transformer)
|
||||
)
|
||||
|
||||
return storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
import Foundation
|
||||
|
||||
/// Save objects to memory based on NSCache
|
||||
final class MemoryStorage {
|
||||
/// Memory cache instance
|
||||
public class MemoryStorage<T>: StorageAware {
|
||||
fileprivate let cache = NSCache<NSString, MemoryCapsule>()
|
||||
// Memory cache keys
|
||||
fileprivate var keys = Set<String>()
|
||||
/// Configuration
|
||||
fileprivate let config: MemoryConfig
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(config: MemoryConfig) {
|
||||
public init(config: MemoryConfig) {
|
||||
self.config = config
|
||||
self.cache.countLimit = Int(config.countLimit)
|
||||
self.cache.totalCostLimit = Int(config.totalCostLimit)
|
||||
}
|
||||
}
|
||||
|
||||
extension MemoryStorage: StorageAware {
|
||||
func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T> {
|
||||
extension MemoryStorage {
|
||||
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) {
|
||||
let capsule = MemoryCapsule(value: object, expiry: .date(expiry?.date ?? config.expiry.date))
|
||||
cache.setObject(capsule, forKey: NSString(string: key))
|
||||
keys.insert(key)
|
||||
}
|
||||
|
||||
public func removeAll() {
|
||||
cache.removeAllObjects()
|
||||
keys.removeAll()
|
||||
}
|
||||
|
||||
public func removeExpiredObjects() {
|
||||
let allKeys = keys
|
||||
for key in allKeys {
|
||||
removeObjectIfExpired(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeObjectIfExpired(forKey key: String) {
|
||||
if let capsule = cache.object(forKey: NSString(string: key)), capsule.expiry.isExpired {
|
||||
removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeObject(forKey key: String) {
|
||||
cache.removeObject(forKey: NSString(string: key))
|
||||
keys.remove(key)
|
||||
}
|
||||
|
||||
public func entry(forKey key: String) throws -> Entry<T> {
|
||||
guard let capsule = cache.object(forKey: NSString(string: key)) else {
|
||||
throw StorageError.notFound
|
||||
}
|
||||
@@ -29,58 +55,11 @@ extension MemoryStorage: StorageAware {
|
||||
|
||||
return Entry(object: object, expiry: capsule.expiry)
|
||||
}
|
||||
|
||||
func removeObject(forKey key: String) {
|
||||
cache.removeObject(forKey: NSString(string: key))
|
||||
keys.remove(key)
|
||||
}
|
||||
|
||||
func setObject<T: Codable>(_ object: T, forKey key: String, expiry: Expiry? = nil) {
|
||||
let capsule = MemoryCapsule(value: object, expiry: .date(expiry?.date ?? config.expiry.date))
|
||||
cache.setObject(capsule, forKey: NSString(string: key))
|
||||
keys.insert(key)
|
||||
}
|
||||
|
||||
func removeAll() {
|
||||
cache.removeAllObjects()
|
||||
keys.removeAll()
|
||||
}
|
||||
|
||||
func removeExpiredObjects() {
|
||||
let allKeys = keys
|
||||
for key in allKeys {
|
||||
removeObjectIfExpired(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MemoryStorage {
|
||||
/**
|
||||
Removes the object from the cache if it's expired.
|
||||
- Parameter key: Unique key to identify the object in the cache
|
||||
*/
|
||||
func removeObjectIfExpired(forKey key: String) {
|
||||
if let capsule = cache.object(forKey: NSString(string: key)), capsule.expiry.isExpired {
|
||||
removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper class to hold cached instance and expiry date.
|
||||
/// Used in memory storage to work with NSCache.
|
||||
class MemoryCapsule: NSObject {
|
||||
/// Object to be cached
|
||||
let object: Any
|
||||
/// Expiration date
|
||||
let expiry: Expiry
|
||||
|
||||
/**
|
||||
Creates a new instance of Capsule.
|
||||
- Parameter value: Object to be cached
|
||||
- Parameter expiry: Expiration date
|
||||
*/
|
||||
init(value: Any, expiry: Expiry) {
|
||||
self.object = value
|
||||
self.expiry = expiry
|
||||
public extension MemoryStorage {
|
||||
func transform<U>() -> MemoryStorage<U> {
|
||||
let storage = MemoryStorage<U>(config: config)
|
||||
return storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
public extension Storage {
|
||||
func transformData() -> Storage<Data> {
|
||||
let storage = transform(transformer: TransformerFactory.forData())
|
||||
return storage
|
||||
}
|
||||
|
||||
func transformImage() -> Storage<Image> {
|
||||
let storage = transform(transformer: TransformerFactory.forImage())
|
||||
return storage
|
||||
}
|
||||
|
||||
func transformCodable<U: Codable>(ofType: U.Type) -> Storage<U> {
|
||||
let storage = transform(transformer: TransformerFactory.forCodable(ofType: U.self))
|
||||
return storage
|
||||
}
|
||||
}
|
||||
@@ -3,63 +3,76 @@ import Dispatch
|
||||
|
||||
/// Manage storage. Use memory storage if specified.
|
||||
/// Synchronous by default. Use `async` for asynchronous operations.
|
||||
public class Storage {
|
||||
public class Storage<T> {
|
||||
/// Used for sync operations
|
||||
fileprivate let sync: StorageAware
|
||||
|
||||
/// Storage used internally by both sync and async storages
|
||||
private let interalStorage: StorageAware
|
||||
let syncStorage: SyncStorage<T>
|
||||
let asyncStorage: AsyncStorage<T>
|
||||
|
||||
/// Initialize storage with configuration options.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - diskConfig: Configuration for disk storage
|
||||
/// - memoryConfig: Optional. Pass confi if you want memory cache
|
||||
/// - memoryConfig: Optional. Pass config if you want memory cache
|
||||
/// - Throws: Throw StorageError if any.
|
||||
public required init(diskConfig: DiskConfig, memoryConfig: MemoryConfig? = nil) throws {
|
||||
// Disk or Hybrid
|
||||
let storage: StorageAware
|
||||
let disk = try DiskStorage(config: diskConfig)
|
||||
public convenience init(diskConfig: DiskConfig, memoryConfig: MemoryConfig, transformer: Transformer<T>) throws {
|
||||
let disk = try DiskStorage(config: diskConfig, transformer: transformer)
|
||||
let memory = MemoryStorage<T>(config: memoryConfig)
|
||||
|
||||
if let memoryConfig = memoryConfig {
|
||||
let memory = MemoryStorage(config: memoryConfig)
|
||||
storage = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
} else {
|
||||
storage = disk
|
||||
}
|
||||
let hybridStorage = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
let syncStorage = SyncStorage(
|
||||
storage: hybridStorage,
|
||||
serialQueue: DispatchQueue(label: "Cache.SyncStorage.SerialQueue")
|
||||
)
|
||||
|
||||
// Wrapper
|
||||
self.interalStorage = TypeWrapperStorage(storage: storage)
|
||||
let asyncStorage = AsyncStorage(
|
||||
storage: hybridStorage,
|
||||
serialQueue: DispatchQueue(label: "Cache.AsyncStorage.SerialQueue")
|
||||
)
|
||||
|
||||
// Sync
|
||||
self.sync = SyncStorage(storage: interalStorage,
|
||||
serialQueue: DispatchQueue(label: "Cache.SyncStorage.SerialQueue"))
|
||||
self.init(syncStorage: syncStorage, asyncStorage: asyncStorage)
|
||||
}
|
||||
|
||||
/// Initialise with sync and async storages
|
||||
///
|
||||
/// - Parameter syncStorage: Synchronous storage
|
||||
/// - Paraeter: asyncStorage: Asynchronous storage
|
||||
public required init(syncStorage: SyncStorage<T>, asyncStorage: AsyncStorage<T>) {
|
||||
self.syncStorage = syncStorage
|
||||
self.asyncStorage = asyncStorage
|
||||
}
|
||||
|
||||
/// Used for async operations
|
||||
public lazy var async: AsyncStorage = AsyncStorage(storage: self.interalStorage,
|
||||
serialQueue: DispatchQueue(label: "Cache.AsyncStorage.SerialQueue"))
|
||||
public lazy var async = self.asyncStorage
|
||||
}
|
||||
|
||||
extension Storage: StorageAware {
|
||||
public func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T> {
|
||||
return try self.sync.entry(ofType: type, forKey: key)
|
||||
public func entry(forKey key: String) throws -> Entry<T> {
|
||||
return try self.syncStorage.entry(forKey: key)
|
||||
}
|
||||
|
||||
public func removeObject(forKey key: String) throws {
|
||||
try self.sync.removeObject(forKey: key)
|
||||
try self.syncStorage.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
public func setObject<T: Codable>(_ object: T, forKey key: String,
|
||||
expiry: Expiry? = nil) throws {
|
||||
try self.sync.setObject(object, forKey: key, expiry: expiry)
|
||||
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
|
||||
try self.syncStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
}
|
||||
|
||||
public func removeAll() throws {
|
||||
try self.sync.removeAll()
|
||||
try self.syncStorage.removeAll()
|
||||
}
|
||||
|
||||
public func removeExpiredObjects() throws {
|
||||
try self.sync.removeExpiredObjects()
|
||||
try self.syncStorage.removeExpiredObjects()
|
||||
}
|
||||
}
|
||||
|
||||
public extension Storage {
|
||||
func transform<U>(transformer: Transformer<U>) -> Storage<U> {
|
||||
let storage = Storage<U>(
|
||||
syncStorage: syncStorage.transform(transformer: transformer),
|
||||
asyncStorage: asyncStorage.transform(transformer: transformer)
|
||||
)
|
||||
return storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,20 @@ import Foundation
|
||||
|
||||
/// A protocol used for saving and loading from storage
|
||||
public protocol StorageAware {
|
||||
associatedtype T
|
||||
/**
|
||||
Tries to retrieve the object from the storage.
|
||||
- Parameter key: Unique key to identify the object in the cache
|
||||
- Returns: Cached object or nil if not found
|
||||
*/
|
||||
func object<T: Codable>(ofType type: T.Type, forKey key: String) throws -> T
|
||||
func object(forKey key: String) throws -> T
|
||||
|
||||
/**
|
||||
Get cache entry which includes object with metadata.
|
||||
- Parameter key: Unique key to identify the object in the cache
|
||||
- Returns: Object wrapper with metadata or nil if not found
|
||||
*/
|
||||
func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T>
|
||||
func entry(forKey key: String) throws -> Entry<T>
|
||||
|
||||
/**
|
||||
Removes the object by the given key.
|
||||
@@ -28,13 +29,13 @@ public protocol StorageAware {
|
||||
- Parameter object: Object that needs to be cached.
|
||||
- Parameter expiry: Overwrite expiry for this object only.
|
||||
*/
|
||||
func setObject<T: Codable>(_ object: T, forKey key: String, expiry: Expiry?) throws
|
||||
func setObject(_ object: T, forKey key: String, expiry: Expiry?) throws
|
||||
|
||||
/**
|
||||
Check if an object exist by the given key.
|
||||
- Parameter key: Unique key to identify the object.
|
||||
*/
|
||||
func existsObject<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Bool
|
||||
func existsObject(forKey key: String) throws -> Bool
|
||||
|
||||
/**
|
||||
Removes all objects from the cache storage.
|
||||
@@ -49,27 +50,27 @@ public protocol StorageAware {
|
||||
/**
|
||||
Check if an expired object by the given key.
|
||||
- Parameter key: Unique key to identify the object.
|
||||
*/
|
||||
func isExpiredObject<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Bool
|
||||
*/
|
||||
func isExpiredObject(forKey key: String) throws -> Bool
|
||||
}
|
||||
|
||||
public extension StorageAware {
|
||||
func object<T: Codable>(ofType type: T.Type, forKey key: String) throws -> T {
|
||||
return try entry(ofType: type, forKey: key).object
|
||||
func object(forKey key: String) throws -> T {
|
||||
return try entry(forKey: key).object
|
||||
}
|
||||
|
||||
func existsObject<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Bool {
|
||||
func existsObject(forKey key: String) throws -> Bool {
|
||||
do {
|
||||
let _: T = try object(ofType: type, forKey: key)
|
||||
let _: T = try object(forKey: key)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isExpiredObject<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Bool {
|
||||
func isExpiredObject(forKey key: String) throws -> Bool {
|
||||
do {
|
||||
let entry = try self.entry(ofType: type, forKey: key)
|
||||
let entry = try self.entry(forKey: key)
|
||||
return entry.expiry.isExpired
|
||||
} catch {
|
||||
return true
|
||||
|
||||
@@ -3,21 +3,21 @@ import Dispatch
|
||||
|
||||
/// Manipulate storage in a "all sync" manner.
|
||||
/// Block the current queue until the operation completes.
|
||||
public class SyncStorage {
|
||||
fileprivate let internalStorage: StorageAware
|
||||
fileprivate let serialQueue: DispatchQueue
|
||||
public class SyncStorage<T> {
|
||||
public let innerStorage: HybridStorage<T>
|
||||
public let serialQueue: DispatchQueue
|
||||
|
||||
init(storage: StorageAware, serialQueue: DispatchQueue) {
|
||||
self.internalStorage = storage
|
||||
public init(storage: HybridStorage<T>, serialQueue: DispatchQueue) {
|
||||
self.innerStorage = storage
|
||||
self.serialQueue = serialQueue
|
||||
}
|
||||
}
|
||||
|
||||
extension SyncStorage: StorageAware {
|
||||
public func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T> {
|
||||
public func entry(forKey key: String) throws -> Entry<T> {
|
||||
var entry: Entry<T>!
|
||||
try serialQueue.sync {
|
||||
entry = try internalStorage.entry(ofType: type, forKey: key)
|
||||
entry = try innerStorage.entry(forKey: key)
|
||||
}
|
||||
|
||||
return entry
|
||||
@@ -25,26 +25,36 @@ extension SyncStorage: StorageAware {
|
||||
|
||||
public func removeObject(forKey key: String) throws {
|
||||
try serialQueue.sync {
|
||||
try self.internalStorage.removeObject(forKey: key)
|
||||
try self.innerStorage.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public func setObject<T: Codable>(_ object: T, forKey key: String,
|
||||
expiry: Expiry? = nil) throws {
|
||||
public func setObject(_ object: T, forKey key: String, expiry: Expiry? = nil) throws {
|
||||
try serialQueue.sync {
|
||||
try internalStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
try innerStorage.setObject(object, forKey: key, expiry: expiry)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeAll() throws {
|
||||
try serialQueue.sync {
|
||||
try internalStorage.removeAll()
|
||||
try innerStorage.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
public func removeExpiredObjects() throws {
|
||||
try serialQueue.sync {
|
||||
try internalStorage.removeExpiredObjects()
|
||||
try innerStorage.removeExpiredObjects()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension SyncStorage {
|
||||
func transform<U>(transformer: Transformer<U>) -> SyncStorage<U> {
|
||||
let storage = SyncStorage<U>(
|
||||
storage: innerStorage.transform(transformer: transformer),
|
||||
serialQueue: serialQueue
|
||||
)
|
||||
|
||||
return storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Deal with top level primitive. Use TypeWrapper as wrapper
|
||||
/// Because we use `JSONEncoder` and `JSONDecoder`.
|
||||
/// Avoid issue like "Top-level T encoded as number JSON fragment"
|
||||
final class TypeWrapperStorage {
|
||||
let internalStorage: StorageAware
|
||||
|
||||
init(storage: StorageAware) {
|
||||
self.internalStorage = storage
|
||||
}
|
||||
}
|
||||
|
||||
extension TypeWrapperStorage: StorageAware {
|
||||
public func entry<T: Codable>(ofType type: T.Type, forKey key: String) throws -> Entry<T> {
|
||||
let wrapperEntry = try internalStorage.entry(ofType: TypeWrapper<T>.self, forKey: key)
|
||||
return Entry(object: wrapperEntry.object.object, expiry: wrapperEntry.expiry, meta: wrapperEntry.meta)
|
||||
}
|
||||
|
||||
public func removeObject(forKey key: String) throws {
|
||||
try internalStorage.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
public func setObject<T: Codable>(_ object: T, forKey key: String,
|
||||
expiry: Expiry? = nil) throws {
|
||||
let wrapper = TypeWrapper<T>(object: object)
|
||||
try internalStorage.setObject(wrapper, forKey: key, expiry: expiry)
|
||||
}
|
||||
|
||||
public func removeAll() throws {
|
||||
try internalStorage.removeAll()
|
||||
}
|
||||
|
||||
public func removeExpiredObjects() throws {
|
||||
try internalStorage.removeExpiredObjects()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to wrap Codable object
|
||||
struct TypeWrapper<T: Codable>: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case object
|
||||
}
|
||||
|
||||
let object: T
|
||||
|
||||
init(object: T) {
|
||||
self.object = object
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ import XCTest
|
||||
@testable import Cache
|
||||
|
||||
final class JSONDecoderExtensionsTests: XCTestCase {
|
||||
private var storage: HybridStorage!
|
||||
private var storage: HybridStorage<User>!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let memory = MemoryStorage(config: MemoryConfig())
|
||||
let disk = try! DiskStorage(config: DiskConfig(name: "HybridDisk"))
|
||||
let memory = MemoryStorage<User>(config: MemoryConfig())
|
||||
let disk = try! DiskStorage<User>(
|
||||
config: DiskConfig(name: "HybridDisk"),
|
||||
transformer: TransformerFactory.forCodable(ofType: User.self)
|
||||
)
|
||||
|
||||
storage = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
}
|
||||
@@ -26,7 +29,7 @@ final class JSONDecoderExtensionsTests: XCTestCase {
|
||||
let user = try JSONDecoder.decode(json, to: User.self)
|
||||
try storage.setObject(user, forKey: "user")
|
||||
|
||||
let cachedObject = try storage.object(ofType: User.self, forKey: "user")
|
||||
let cachedObject = try storage.object(forKey: "user")
|
||||
XCTAssertEqual(user, cachedObject)
|
||||
}
|
||||
|
||||
@@ -36,7 +39,7 @@ final class JSONDecoderExtensionsTests: XCTestCase {
|
||||
let user = try JSONDecoder.decode(string, to: User.self)
|
||||
try storage.setObject(user, forKey: "user")
|
||||
|
||||
let cachedObject = try storage.object(ofType: User.self, forKey: "user")
|
||||
let cachedObject = try storage.object(forKey: "user")
|
||||
XCTAssertEqual(cachedObject.firstName, "John")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@ import Dispatch
|
||||
@testable import Cache
|
||||
|
||||
final class AsyncStorageTests: XCTestCase {
|
||||
private var storage: AsyncStorage!
|
||||
private var storage: AsyncStorage<User>!
|
||||
let user = User(firstName: "John", lastName: "Snow")
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let memory = MemoryStorage(config: MemoryConfig())
|
||||
let disk = try! DiskStorage(config: DiskConfig(name: "Async Disk"))
|
||||
let hybrid = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
let primitive = TypeWrapperStorage(storage: hybrid)
|
||||
storage = AsyncStorage(storage: primitive, serialQueue: DispatchQueue(label: "Async"))
|
||||
let memory = MemoryStorage<User>(config: MemoryConfig())
|
||||
let disk = try! DiskStorage<User>(config: DiskConfig(name: "Async Disk"), transformer: TransformerFactory.forCodable(ofType: User.self))
|
||||
let hybrid = HybridStorage<User>(memoryStorage: memory, diskStorage: disk)
|
||||
storage = AsyncStorage(storage: hybrid, serialQueue: DispatchQueue(label: "Async"))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -24,7 +23,7 @@ final class AsyncStorageTests: XCTestCase {
|
||||
let expectation = self.expectation(description: #function)
|
||||
|
||||
storage.setObject(user, forKey: "user", completion: { _ in })
|
||||
storage.object(ofType: User.self, forKey: "user", completion: { result in
|
||||
storage.object(forKey: "user", completion: { result in
|
||||
switch result {
|
||||
case .value(let cachedUser):
|
||||
XCTAssertEqual(cachedUser, self.user)
|
||||
@@ -38,19 +37,20 @@ final class AsyncStorageTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRemoveAll() {
|
||||
let intStorage = storage.transform(transformer: TransformerFactory.forCodable(ofType: Int.self))
|
||||
let expectation = self.expectation(description: #function)
|
||||
given("add a lot of objects") {
|
||||
Array(0..<100).forEach {
|
||||
storage.setObject($0, forKey: "key-\($0)", completion: { _ in })
|
||||
intStorage.setObject($0, forKey: "key-\($0)", completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
when("remove all") {
|
||||
storage.removeAll(completion: { _ in })
|
||||
intStorage.removeAll(completion: { _ in })
|
||||
}
|
||||
|
||||
then("all are removed") {
|
||||
storage.existsObject(ofType: Int.self, forKey: "key-99", completion: { result in
|
||||
intStorage.existsObject(forKey: "key-99", completion: { result in
|
||||
switch result {
|
||||
case .value:
|
||||
XCTFail()
|
||||
|
||||
@@ -5,12 +5,12 @@ final class DiskStorageTests: XCTestCase {
|
||||
private let key = "youknownothing"
|
||||
private let testObject = User(firstName: "John", lastName: "Snow")
|
||||
private let fileManager = FileManager()
|
||||
private var storage: DiskStorage!
|
||||
private var storage: DiskStorage<User>!
|
||||
private let config = DiskConfig(name: "Floppy")
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
storage = try! DiskStorage(config: config)
|
||||
storage = try! DiskStorage<User>(config: config, transformer: TransformerFactory.forCodable(ofType: User.self))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -30,7 +30,7 @@ final class DiskStorageTests: XCTestCase {
|
||||
/// Test that it returns the correct path
|
||||
func testDefaultPath() {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(
|
||||
.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true
|
||||
.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true
|
||||
)
|
||||
let path = "\(paths.first!)/\(config.name.capitalized)"
|
||||
XCTAssertEqual(storage.path, path)
|
||||
@@ -47,7 +47,7 @@ final class DiskStorageTests: XCTestCase {
|
||||
|
||||
let customConfig = DiskConfig(name: "SSD", directory: url)
|
||||
|
||||
storage = try DiskStorage(config: customConfig)
|
||||
storage = try DiskStorage<User>(config: customConfig, transformer: TransformerFactory.forCodable(ofType: User.self))
|
||||
|
||||
XCTAssertEqual(
|
||||
storage.path,
|
||||
@@ -77,13 +77,13 @@ final class DiskStorageTests: XCTestCase {
|
||||
// Returns nil if entry doesn't exist
|
||||
var entry: Entry<User>?
|
||||
do {
|
||||
entry = try storage.entry(ofType: User.self, forKey: key)
|
||||
entry = try storage.entry(forKey: key)
|
||||
} catch {}
|
||||
XCTAssertNil(entry)
|
||||
|
||||
// Returns entry if object exists
|
||||
try storage.setObject(testObject, forKey: key)
|
||||
entry = try storage.entry(ofType: User.self, forKey: key)
|
||||
entry = try storage.entry(forKey: key)
|
||||
let attributes = try fileManager.attributesOfItem(atPath: storage.makeFilePath(for: key))
|
||||
let expiry = Expiry.date(attributes[FileAttributeKey.modificationDate] as! Date)
|
||||
|
||||
@@ -95,7 +95,7 @@ final class DiskStorageTests: XCTestCase {
|
||||
/// Test that it resolves cached object
|
||||
func testSetObject() throws {
|
||||
try storage.setObject(testObject, forKey: key)
|
||||
let cachedObject: User? = try storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject: User? = try storage.object(forKey: key)
|
||||
|
||||
XCTAssertEqual(cachedObject?.firstName, testObject.firstName)
|
||||
XCTAssertEqual(cachedObject?.lastName, testObject.lastName)
|
||||
@@ -116,7 +116,7 @@ final class DiskStorageTests: XCTestCase {
|
||||
try storage.removeObjectIfExpired(forKey: key)
|
||||
var cachedObject: User?
|
||||
do {
|
||||
cachedObject = try storage.object(ofType: User.self, forKey: key)
|
||||
cachedObject = try storage.object(forKey: key)
|
||||
} catch {}
|
||||
|
||||
XCTAssertNil(cachedObject)
|
||||
@@ -126,16 +126,16 @@ final class DiskStorageTests: XCTestCase {
|
||||
func testRemoveObjectIfExpiredWhenNotExpired() throws {
|
||||
try storage.setObject(testObject, forKey: key)
|
||||
try storage.removeObjectIfExpired(forKey: key)
|
||||
let cachedObject: User? = try storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject: User? = try storage.object(forKey: key)
|
||||
XCTAssertNotNil(cachedObject)
|
||||
}
|
||||
|
||||
/// Test expired object
|
||||
func testExpiredObject() throws {
|
||||
try storage.setObject(testObject, forKey: key, expiry: .seconds(0.9))
|
||||
XCTAssertFalse(try! storage.isExpiredObject(ofType: User.self, forKey: key))
|
||||
XCTAssertFalse(try! storage.isExpiredObject(forKey: key))
|
||||
sleep(1)
|
||||
XCTAssertTrue(try! storage.isExpiredObject(ofType: User.self, forKey: key))
|
||||
XCTAssertTrue(try! storage.isExpiredObject(forKey: key))
|
||||
}
|
||||
|
||||
/// Test that it clears cache directory
|
||||
@@ -185,10 +185,10 @@ final class DiskStorageTests: XCTestCase {
|
||||
try storage.setObject(testObject, forKey: key2, expiry: expiry2)
|
||||
try storage.removeExpiredObjects()
|
||||
var object1: User?
|
||||
let object2 = try storage.object(ofType: User.self, forKey: key2)
|
||||
let object2 = try storage.object(forKey: key2)
|
||||
|
||||
do {
|
||||
object1 = try storage.object(ofType: User.self, forKey: key1)
|
||||
object1 = try storage.object(forKey: key1)
|
||||
} catch {}
|
||||
|
||||
XCTAssertNil(object1)
|
||||
|
||||
@@ -5,13 +5,13 @@ final class HybridStorageTests: XCTestCase {
|
||||
private let cacheName = "WeirdoCache"
|
||||
private let key = "alongweirdkey"
|
||||
private let testObject = User(firstName: "John", lastName: "Targaryen")
|
||||
private var storage: HybridStorage!
|
||||
private var storage: HybridStorage<User>!
|
||||
private let fileManager = FileManager()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let memory = MemoryStorage(config: MemoryConfig())
|
||||
let disk = try! DiskStorage(config: DiskConfig(name: "HybridDisk"))
|
||||
let memory = MemoryStorage<User>(config: MemoryConfig())
|
||||
let disk = try! DiskStorage<User>(config: DiskConfig(name: "HybridDisk"), transformer: TransformerFactory.forCodable(ofType: User.self))
|
||||
|
||||
storage = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
}
|
||||
@@ -24,17 +24,17 @@ final class HybridStorageTests: XCTestCase {
|
||||
func testSetObject() throws {
|
||||
try when("set to storage") {
|
||||
try storage.setObject(testObject, forKey: key)
|
||||
let cachedObject = try storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try storage.object(forKey: key)
|
||||
XCTAssertEqual(cachedObject, testObject)
|
||||
}
|
||||
|
||||
try then("it is set to memory too") {
|
||||
let memoryObject = try storage.memoryStorage.object(ofType: User.self, forKey: key)
|
||||
let memoryObject = try storage.memoryStorage.object(forKey: key)
|
||||
XCTAssertNotNil(memoryObject)
|
||||
}
|
||||
|
||||
try then("it is set to disk too") {
|
||||
let diskObject = try storage.diskStorage.object(ofType: User.self, forKey: key)
|
||||
let diskObject = try storage.diskStorage.object(forKey: key)
|
||||
XCTAssertNotNil(diskObject)
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ final class HybridStorageTests: XCTestCase {
|
||||
func testEntry() throws {
|
||||
let expiryDate = Date()
|
||||
try storage.setObject(testObject, forKey: key, expiry: .date(expiryDate))
|
||||
let entry = try storage.entry(ofType: User.self, forKey: key)
|
||||
let entry = try storage.entry(forKey: key)
|
||||
|
||||
XCTAssertEqual(entry.object, testObject)
|
||||
XCTAssertEqual(entry.expiry.date, expiryDate)
|
||||
@@ -52,12 +52,12 @@ final class HybridStorageTests: XCTestCase {
|
||||
func testObjectCopyToMemory() throws {
|
||||
try when("set to disk only") {
|
||||
try storage.diskStorage.setObject(testObject, forKey: key)
|
||||
let cachedObject: User = try storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject: User = try storage.object(forKey: key)
|
||||
XCTAssertEqual(cachedObject, testObject)
|
||||
}
|
||||
|
||||
try then("there is no object in memory") {
|
||||
let inMemoryCachedObject = try storage.memoryStorage.object(ofType: User.self, forKey: key)
|
||||
let inMemoryCachedObject = try storage.memoryStorage.object(forKey: key)
|
||||
XCTAssertEqual(inMemoryCachedObject, testObject)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ final class HybridStorageTests: XCTestCase {
|
||||
let date = Date().addingTimeInterval(3)
|
||||
try when("set to disk only") {
|
||||
try storage.diskStorage.setObject(testObject, forKey: key, expiry: .seconds(3))
|
||||
let entry = try storage.entry(ofType: User.self, forKey: key)
|
||||
let entry = try storage.entry(forKey: key)
|
||||
//accuracy for slow disk processes
|
||||
XCTAssertEqual(entry.expiry.date.timeIntervalSinceReferenceDate,
|
||||
date.timeIntervalSinceReferenceDate,
|
||||
@@ -74,7 +74,7 @@ final class HybridStorageTests: XCTestCase {
|
||||
}
|
||||
|
||||
try then("there is no object in memory") {
|
||||
let entry = try storage.memoryStorage.entry(ofType: User.self, forKey: key)
|
||||
let entry = try storage.memoryStorage.entry(forKey: key)
|
||||
//accuracy for slow disk processes
|
||||
XCTAssertEqual(entry.expiry.date.timeIntervalSinceReferenceDate,
|
||||
date.timeIntervalSinceReferenceDate,
|
||||
@@ -86,22 +86,22 @@ final class HybridStorageTests: XCTestCase {
|
||||
func testRemoveObject() throws {
|
||||
try given("set to storage") {
|
||||
try storage.setObject(testObject, forKey: key)
|
||||
XCTAssertNotNil(try storage.object(ofType: User.self, forKey: key))
|
||||
XCTAssertNotNil(try storage.object(forKey: key))
|
||||
}
|
||||
|
||||
try when("remove object from storage") {
|
||||
try storage.removeObject(forKey: key)
|
||||
let cachedObject = try? storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try? storage.object(forKey: key)
|
||||
XCTAssertNil(cachedObject)
|
||||
}
|
||||
|
||||
then("there is no object in memory") {
|
||||
let memoryObject = try? storage.memoryStorage.object(ofType: User.self, forKey: key)
|
||||
let memoryObject = try? storage.memoryStorage.object(forKey: key)
|
||||
XCTAssertNil(memoryObject)
|
||||
}
|
||||
|
||||
then("there is no object on disk") {
|
||||
let diskObject = try? storage.diskStorage.object(ofType: User.self, forKey: key)
|
||||
let diskObject = try? storage.diskStorage.object(forKey: key)
|
||||
XCTAssertNil(diskObject)
|
||||
}
|
||||
}
|
||||
@@ -111,16 +111,16 @@ final class HybridStorageTests: XCTestCase {
|
||||
try when("set and remove all") {
|
||||
try storage.setObject(testObject, forKey: key)
|
||||
try storage.removeAll()
|
||||
XCTAssertNil(try? storage.object(ofType: User.self, forKey: key))
|
||||
XCTAssertNil(try? storage.object(forKey: key))
|
||||
}
|
||||
|
||||
then("there is no object in memory") {
|
||||
let memoryObject = try? storage.memoryStorage.object(ofType: User.self, forKey: key)
|
||||
let memoryObject = try? storage.memoryStorage.object(forKey: key)
|
||||
XCTAssertNil(memoryObject)
|
||||
}
|
||||
|
||||
then("there is no object on disk") {
|
||||
let diskObject = try? storage.diskStorage.object(ofType: User.self, forKey: key)
|
||||
let diskObject = try? storage.diskStorage.object(forKey: key)
|
||||
XCTAssertNil(diskObject)
|
||||
}
|
||||
}
|
||||
@@ -152,8 +152,8 @@ final class HybridStorageTests: XCTestCase {
|
||||
}
|
||||
|
||||
then("object with key2 survived") {
|
||||
XCTAssertNil(try? storage.object(ofType: User.self, forKey: key1))
|
||||
XCTAssertNotNil(try? storage.object(ofType: User.self, forKey: key2))
|
||||
XCTAssertNil(try? storage.object(forKey: key1))
|
||||
XCTAssertNotNil(try? storage.object(forKey: key2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import XCTest
|
||||
final class MemoryStorageTests: XCTestCase {
|
||||
private let key = "youknownothing"
|
||||
private let testObject = User(firstName: "John", lastName: "Snow")
|
||||
private var storage: MemoryStorage!
|
||||
private var storage: MemoryStorage<User>!
|
||||
private let config = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
storage = MemoryStorage(config: config)
|
||||
storage = MemoryStorage<User>(config: config)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -20,7 +20,7 @@ final class MemoryStorageTests: XCTestCase {
|
||||
/// Test that it saves an object
|
||||
func testSetObject() {
|
||||
storage.setObject(testObject, forKey: key)
|
||||
let cachedObject = try! storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try! storage.object(forKey: key)
|
||||
XCTAssertNotNil(cachedObject)
|
||||
XCTAssertEqual(cachedObject.firstName, testObject.firstName)
|
||||
XCTAssertEqual(cachedObject.lastName, testObject.lastName)
|
||||
@@ -28,12 +28,12 @@ final class MemoryStorageTests: XCTestCase {
|
||||
|
||||
func testCacheEntry() {
|
||||
// Returns nil if entry doesn't exist
|
||||
var entry = try? storage.entry(ofType: User.self, forKey: key)
|
||||
var entry = try? storage.entry(forKey: key)
|
||||
XCTAssertNil(entry)
|
||||
|
||||
// Returns entry if object exists
|
||||
storage.setObject(testObject, forKey: key)
|
||||
entry = try! storage.entry(ofType: User.self, forKey: key)
|
||||
entry = try! storage.entry(forKey: key)
|
||||
|
||||
XCTAssertEqual(entry?.object.firstName, testObject.firstName)
|
||||
XCTAssertEqual(entry?.object.lastName, testObject.lastName)
|
||||
@@ -43,13 +43,13 @@ final class MemoryStorageTests: XCTestCase {
|
||||
func testSetObjectWithExpiry() {
|
||||
let date = Date().addingTimeInterval(1)
|
||||
storage.setObject(testObject, forKey: key, expiry: .seconds(1))
|
||||
var entry = try! storage.entry(ofType: User.self, forKey: key)
|
||||
var entry = try! storage.entry(forKey: key)
|
||||
XCTAssertEqual(entry.expiry.date.timeIntervalSinceReferenceDate,
|
||||
date.timeIntervalSinceReferenceDate,
|
||||
accuracy: 0.1)
|
||||
//Timer vs sleep: do not complicate
|
||||
sleep(1)
|
||||
entry = try! storage.entry(ofType: User.self, forKey: key)
|
||||
entry = try! storage.entry(forKey: key)
|
||||
XCTAssertEqual(entry.expiry.date.timeIntervalSinceReferenceDate,
|
||||
date.timeIntervalSinceReferenceDate,
|
||||
accuracy: 0.1)
|
||||
@@ -59,7 +59,7 @@ final class MemoryStorageTests: XCTestCase {
|
||||
func testRemoveObject() {
|
||||
storage.setObject(testObject, forKey: key)
|
||||
storage.removeObject(forKey: key)
|
||||
let cachedObject = try? storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try? storage.object(forKey: key)
|
||||
XCTAssertNil(cachedObject)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ final class MemoryStorageTests: XCTestCase {
|
||||
let expiry: Expiry = .date(Date().addingTimeInterval(-10))
|
||||
storage.setObject(testObject, forKey: key, expiry: expiry)
|
||||
storage.removeObjectIfExpired(forKey: key)
|
||||
let cachedObject = try? storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try? storage.object(forKey: key)
|
||||
|
||||
XCTAssertNil(cachedObject)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ final class MemoryStorageTests: XCTestCase {
|
||||
func testRemoveObjectIfExpiredWhenNotExpired() {
|
||||
storage.setObject(testObject, forKey: key)
|
||||
storage.removeObjectIfExpired(forKey: key)
|
||||
let cachedObject = try! storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try! storage.object(forKey: key)
|
||||
|
||||
XCTAssertNotNil(cachedObject)
|
||||
}
|
||||
@@ -85,16 +85,16 @@ final class MemoryStorageTests: XCTestCase {
|
||||
/// Test expired object
|
||||
func testExpiredObject() throws {
|
||||
storage.setObject(testObject, forKey: key, expiry: .seconds(0.9))
|
||||
XCTAssertFalse(try! storage.isExpiredObject(ofType: User.self, forKey: key))
|
||||
XCTAssertFalse(try! storage.isExpiredObject(forKey: key))
|
||||
sleep(1)
|
||||
XCTAssertTrue(try! storage.isExpiredObject(ofType: User.self, forKey: key))
|
||||
XCTAssertTrue(try! storage.isExpiredObject(forKey: key))
|
||||
}
|
||||
|
||||
/// Test that it clears cache directory
|
||||
func testRemoveAll() {
|
||||
storage.setObject(testObject, forKey: key)
|
||||
storage.removeAll()
|
||||
let cachedObject = try? storage.object(ofType: User.self, forKey: key)
|
||||
let cachedObject = try? storage.object(forKey: key)
|
||||
XCTAssertNil(cachedObject)
|
||||
}
|
||||
|
||||
@@ -107,8 +107,8 @@ final class MemoryStorageTests: XCTestCase {
|
||||
storage.setObject(testObject, forKey: key1, expiry: expiry1)
|
||||
storage.setObject(testObject, forKey: key2, expiry: expiry2)
|
||||
storage.removeExpiredObjects()
|
||||
let object1 = try? storage.object(ofType: User.self, forKey: key1)
|
||||
let object2 = try! storage.object(ofType: User.self, forKey: key2)
|
||||
let object1 = try? storage.object(forKey: key1)
|
||||
let object2 = try! storage.object(forKey: key2)
|
||||
|
||||
XCTAssertNil(object1)
|
||||
XCTAssertNotNil(object2)
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import XCTest
|
||||
@testable import Cache
|
||||
|
||||
final class StorageSupportTests: XCTestCase {
|
||||
private var storage: HybridStorage<Bool>!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let memory = MemoryStorage<Bool>(config: MemoryConfig())
|
||||
let disk = try! DiskStorage<Bool>(config: DiskConfig(name: "PrimitiveDisk"), transformer: TransformerFactory.forCodable(ofType: Bool.self))
|
||||
storage = HybridStorage<Bool>(memoryStorage: memory, diskStorage: disk)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
try? storage.removeAll()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testSetPrimitive() throws {
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Bool.self))
|
||||
try s.setObject(true, forKey: "bool")
|
||||
XCTAssertEqual(try s.object(forKey: "bool"), true)
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: [Bool].self))
|
||||
try s.setObject([true, false, true], forKey: "array of bools")
|
||||
XCTAssertEqual(try s.object(forKey: "array of bools"), [true, false, true])
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: String.self))
|
||||
try s.setObject("one", forKey: "string")
|
||||
XCTAssertEqual(try s.object(forKey: "string"), "one")
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: [String].self))
|
||||
try s.setObject(["one", "two", "three"], forKey: "array of strings")
|
||||
XCTAssertEqual(try s.object(forKey: "array of strings"), ["one", "two", "three"])
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Int.self))
|
||||
try s.setObject(10, forKey: "int")
|
||||
XCTAssertEqual(try s.object(forKey: "int"), 10)
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: [Int].self))
|
||||
try s.setObject([1, 2, 3], forKey: "array of ints")
|
||||
XCTAssertEqual(try s.object(forKey: "array of ints"), [1, 2, 3])
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Float.self))
|
||||
let float: Float = 1.1
|
||||
try s.setObject(float, forKey: "float")
|
||||
XCTAssertEqual(try s.object(forKey: "float"), float)
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: [Float].self))
|
||||
let floats: [Float] = [1.1, 1.2, 1.3]
|
||||
try s.setObject(floats, forKey: "array of floats")
|
||||
XCTAssertEqual(try s.object(forKey: "array of floats"), floats)
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Double.self))
|
||||
let double: Double = 1.1
|
||||
try s.setObject(double, forKey: "double")
|
||||
XCTAssertEqual(try s.object(forKey: "double"), double)
|
||||
}
|
||||
|
||||
do {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: [Double].self))
|
||||
let doubles: [Double] = [1.1, 1.2, 1.3]
|
||||
try s.setObject(doubles, forKey: "array of doubles")
|
||||
XCTAssertEqual(try s.object(forKey: "array of doubles"), doubles)
|
||||
}
|
||||
}
|
||||
|
||||
func testSetData() {
|
||||
let s = storage.transform(transformer: TransformerFactory.forData())
|
||||
|
||||
do {
|
||||
let string = "Hello"
|
||||
let data = string.data(using: .utf8)!
|
||||
try s.setObject(data, forKey: "data")
|
||||
|
||||
let cachedObject = try s.object(forKey: "data")
|
||||
let cachedString = String(data: cachedObject, encoding: .utf8)
|
||||
|
||||
XCTAssertEqual(cachedString, string)
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testSetDate() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Date.self))
|
||||
|
||||
let date = Date(timeIntervalSince1970: 100)
|
||||
try s.setObject(date, forKey: "date")
|
||||
let cachedObject = try s.object(forKey: "date")
|
||||
|
||||
XCTAssertEqual(date, cachedObject)
|
||||
}
|
||||
|
||||
func testSetURL() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: URL.self))
|
||||
let url = URL(string: "https://hyper.no")!
|
||||
try s.setObject(url, forKey: "url")
|
||||
let cachedObject = try s.object(forKey: "url")
|
||||
|
||||
XCTAssertEqual(url, cachedObject)
|
||||
}
|
||||
|
||||
func testWithSet() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Set<Int>.self))
|
||||
let set = Set<Int>(arrayLiteral: 1, 2, 3)
|
||||
try s.setObject(set, forKey: "set")
|
||||
XCTAssertEqual(try s.object(forKey: "set") as Set<Int>, set)
|
||||
}
|
||||
|
||||
func testWithSimpleDictionary() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: [String: Int].self))
|
||||
|
||||
let dict: [String: Int] = [
|
||||
"key1": 1,
|
||||
"key2": 2
|
||||
]
|
||||
|
||||
try s.setObject(dict, forKey: "dict")
|
||||
let cachedObject = try s.object(forKey: "dict") as [String: Int]
|
||||
XCTAssertEqual(cachedObject, dict)
|
||||
}
|
||||
|
||||
func testWithComplexDictionary() {
|
||||
let _: [String: Any] = [
|
||||
"key1": 1,
|
||||
"key2": 2
|
||||
]
|
||||
|
||||
// fatal error: Dictionary<String, Any> does not conform to Encodable because Any does not conform to Encodable
|
||||
// try storage.setObject(dict, forKey: "dict")
|
||||
}
|
||||
|
||||
func testIntFloat() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Float.self))
|
||||
let key = "key"
|
||||
try s.setObject(10, forKey: key)
|
||||
|
||||
try then("Casting to int or float is the same") {
|
||||
XCTAssertEqual(try s.object(forKey: key), 10)
|
||||
|
||||
let intStorage = s.transform(transformer: TransformerFactory.forCodable(ofType: Int.self))
|
||||
XCTAssertEqual(try intStorage.object(forKey: key), 10)
|
||||
}
|
||||
}
|
||||
|
||||
func testFloatDouble() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: Float.self))
|
||||
let key = "key"
|
||||
try s.setObject(10.5, forKey: key)
|
||||
|
||||
try then("Casting to float or double is the same") {
|
||||
XCTAssertEqual(try s.object(forKey: key), 10.5)
|
||||
|
||||
let doubleStorage = s.transform(transformer: TransformerFactory.forCodable(ofType: Double.self))
|
||||
XCTAssertEqual(try doubleStorage.object(forKey: key), 10.5)
|
||||
}
|
||||
}
|
||||
|
||||
func testCastingToAnotherType() throws {
|
||||
let s = storage.transform(transformer: TransformerFactory.forCodable(ofType: String.self))
|
||||
try s.setObject("Hello", forKey: "string")
|
||||
|
||||
do {
|
||||
let intStorage = s.transform(transformer: TransformerFactory.forCodable(ofType: Int.self))
|
||||
let _ = try intStorage.object(forKey: "string")
|
||||
XCTFail()
|
||||
} catch {
|
||||
XCTAssertTrue(error is DecodingError)
|
||||
}
|
||||
}
|
||||
|
||||
func testOverridenOnDisk() throws {
|
||||
let intStorage = storage.transform(transformer: TransformerFactory.forCodable(ofType: Int.self))
|
||||
let stringStorage = storage.transform(transformer: TransformerFactory.forCodable(ofType: String.self))
|
||||
|
||||
let key = "sameKey"
|
||||
|
||||
try intStorage.setObject(1, forKey: key)
|
||||
try stringStorage.setObject("hello world", forKey: key)
|
||||
|
||||
let intValue = try? intStorage.diskStorage.object(forKey: key)
|
||||
let stringValue = try? stringStorage.diskStorage.object(forKey: key)
|
||||
|
||||
XCTAssertNil(intValue)
|
||||
XCTAssertNotNil(stringValue)
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,17 @@ import Dispatch
|
||||
@testable import Cache
|
||||
|
||||
final class SyncStorageTests: XCTestCase {
|
||||
private var storage: SyncStorage!
|
||||
private var storage: SyncStorage<User>!
|
||||
let user = User(firstName: "John", lastName: "Snow")
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let memory = MemoryStorage(config: MemoryConfig())
|
||||
let primitive = TypeWrapperStorage(storage: memory)
|
||||
storage = SyncStorage(storage: primitive, serialQueue: DispatchQueue(label: "Sync"))
|
||||
|
||||
let memory = MemoryStorage<User>(config: MemoryConfig())
|
||||
let disk = try! DiskStorage<User>(config: DiskConfig(name: "HybridDisk"), transformer: TransformerFactory.forCodable(ofType: User.self))
|
||||
|
||||
let hybridStorage = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
storage = SyncStorage(storage: hybridStorage, serialQueue: DispatchQueue(label: "Sync"))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -20,24 +23,25 @@ final class SyncStorageTests: XCTestCase {
|
||||
|
||||
func testSetObject() throws {
|
||||
try storage.setObject(user, forKey: "user")
|
||||
let cachedObject = try storage.object(ofType: User.self, forKey: "user")
|
||||
let cachedObject = try storage.object(forKey: "user")
|
||||
|
||||
XCTAssertEqual(cachedObject, user)
|
||||
}
|
||||
|
||||
func testRemoveAll() throws {
|
||||
let intStorage = storage.transform(transformer: TransformerFactory.forCodable(ofType: Int.self))
|
||||
try given("add a lot of objects") {
|
||||
try Array(0..<100).forEach {
|
||||
try storage.setObject($0, forKey: "key-\($0)")
|
||||
try intStorage.setObject($0, forKey: "key-\($0)")
|
||||
}
|
||||
}
|
||||
|
||||
try when("remove all") {
|
||||
try storage.removeAll()
|
||||
try intStorage.removeAll()
|
||||
}
|
||||
|
||||
try then("all are removed") {
|
||||
XCTAssertFalse(try storage.existsObject(ofType: Int.self, forKey: "key-99"))
|
||||
XCTAssertFalse(try intStorage.existsObject(forKey: "key-99"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
import XCTest
|
||||
@testable import Cache
|
||||
|
||||
final class TypeWrapperStorageTests: XCTestCase {
|
||||
private var storage: TypeWrapperStorage!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let memory = MemoryStorage(config: MemoryConfig())
|
||||
let disk = try! DiskStorage(config: DiskConfig(name: "PrimitiveDisk"))
|
||||
let hybrid = HybridStorage(memoryStorage: memory, diskStorage: disk)
|
||||
storage = TypeWrapperStorage(storage: hybrid)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
try? storage.removeAll()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testSetPrimitive() throws {
|
||||
try storage.setObject(true, forKey: "bool")
|
||||
XCTAssertEqual(try storage.object(ofType: Bool.self, forKey: "bool"), true)
|
||||
|
||||
try storage.setObject([true, false, true], forKey: "array of bools")
|
||||
XCTAssertEqual(try storage.object(ofType: [Bool].self, forKey: "array of bools"), [true, false, true])
|
||||
|
||||
try storage.setObject("one", forKey: "string")
|
||||
XCTAssertEqual(try storage.object(ofType: String.self, forKey: "string"), "one")
|
||||
|
||||
try storage.setObject(["one", "two", "three"], forKey: "array of strings")
|
||||
XCTAssertEqual(try storage.object(ofType: [String].self, forKey: "array of strings"), ["one", "two", "three"])
|
||||
|
||||
try storage.setObject(10, forKey: "int")
|
||||
XCTAssertEqual(try storage.object(ofType: Int.self, forKey: "int"), 10)
|
||||
|
||||
try storage.setObject([1, 2, 3], forKey: "array of ints")
|
||||
XCTAssertEqual(try storage.object(ofType: [Int].self, forKey: "array of ints"), [1, 2, 3])
|
||||
|
||||
let float: Float = 1.1
|
||||
try storage.setObject(float, forKey: "float")
|
||||
XCTAssertEqual(try storage.object(ofType: Float.self, forKey: "float"), float)
|
||||
|
||||
let floats: [Float] = [1.1, 1.2, 1.3]
|
||||
try storage.setObject(floats, forKey: "array of floats")
|
||||
XCTAssertEqual(try storage.object(ofType: [Float].self, forKey: "array of floats"), floats)
|
||||
|
||||
let double: Double = 1.1
|
||||
try storage.setObject(double, forKey: "double")
|
||||
XCTAssertEqual(try storage.object(ofType: Double.self, forKey: "double"), double)
|
||||
|
||||
let doubles: [Double] = [1.1, 1.2, 1.3]
|
||||
try storage.setObject(doubles, forKey: "array of doubles")
|
||||
XCTAssertEqual(try storage.object(ofType: [Double].self, forKey: "array of doubles"), doubles)
|
||||
}
|
||||
|
||||
func testSetData() {
|
||||
do {
|
||||
let string = "Hello"
|
||||
let data = string.data(using: .utf8)
|
||||
try storage.setObject(data, forKey: "data")
|
||||
|
||||
let cachedObject = try storage.object(ofType: Data.self, forKey: "data")
|
||||
let cachedString = String(data: cachedObject, encoding: .utf8)
|
||||
|
||||
XCTAssertEqual(cachedString, string)
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testSetDate() throws {
|
||||
let date = Date(timeIntervalSince1970: 100)
|
||||
try storage.setObject(date, forKey: "date")
|
||||
let cachedObject = try storage.object(ofType: Date.self, forKey: "date")
|
||||
|
||||
XCTAssertEqual(date, cachedObject)
|
||||
}
|
||||
|
||||
func testSetURL() throws {
|
||||
let url = URL(string: "https://hyper.no")
|
||||
try storage.setObject(url, forKey: "url")
|
||||
let cachedObject = try storage.object(ofType: URL.self, forKey: "url")
|
||||
|
||||
XCTAssertEqual(url, cachedObject)
|
||||
}
|
||||
|
||||
func testWithSet() throws {
|
||||
let set = Set<Int>(arrayLiteral: 1, 2, 3)
|
||||
try storage.setObject(set, forKey: "set")
|
||||
XCTAssertEqual(try storage.object(ofType: Set<Int>.self, forKey: "set") as Set<Int>, set)
|
||||
}
|
||||
|
||||
func testWithSimpleDictionary() throws {
|
||||
let dict: [String: Int] = [
|
||||
"key1": 1,
|
||||
"key2": 2
|
||||
]
|
||||
|
||||
try storage.setObject(dict, forKey: "dict")
|
||||
let cachedObject = try storage.object(ofType: [String: Int].self, forKey: "dict") as [String: Int]
|
||||
XCTAssertEqual(cachedObject, dict)
|
||||
}
|
||||
|
||||
func testWithComplexDictionary() {
|
||||
let _: [String: Any] = [
|
||||
"key1": 1,
|
||||
"key2": 2
|
||||
]
|
||||
|
||||
// fatal error: Dictionary<String, Any> does not conform to Encodable because Any does not conform to Encodable
|
||||
// try storage.setObject(dict, forKey: "dict")
|
||||
}
|
||||
|
||||
func testSameKey() throws {
|
||||
let user = User(firstName: "John", lastName: "Snow")
|
||||
let key = "keyMadeOfDragonGlass"
|
||||
try storage.setObject(user, forKey: key)
|
||||
try storage.setObject("Dragonstone", forKey: key)
|
||||
|
||||
XCTAssertNil(try? storage.object(ofType: User.self, forKey: key))
|
||||
XCTAssertNotNil(try storage.object(ofType: String.self, forKey: key))
|
||||
}
|
||||
|
||||
func testIntFloat() throws {
|
||||
let key = "key"
|
||||
try storage.setObject(10, forKey: key)
|
||||
|
||||
try then("Casting to int or float is the same") {
|
||||
XCTAssertEqual(try storage.object(ofType: Int.self, forKey: key), 10)
|
||||
XCTAssertEqual(try storage.object(ofType: Float.self, forKey: key), 10)
|
||||
}
|
||||
}
|
||||
|
||||
func testFloatDouble() throws {
|
||||
let key = "key"
|
||||
try storage.setObject(10.5, forKey: key)
|
||||
|
||||
try then("Casting to float or double is the same") {
|
||||
XCTAssertEqual(try storage.object(ofType: Float.self, forKey: key), 10.5)
|
||||
XCTAssertEqual(try storage.object(ofType: Double.self, forKey: key), 10.5)
|
||||
}
|
||||
}
|
||||
|
||||
func testCastingToAnotherType() throws {
|
||||
try storage.setObject("Hello", forKey: "string")
|
||||
|
||||
do {
|
||||
let cachedObject = try storage.object(ofType: Int.self, forKey: "string")
|
||||
XCTAssertEqual(cachedObject, 10)
|
||||
} catch {
|
||||
XCTAssertTrue(error is DecodingError)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user