diff --git a/Cache.xcodeproj/project.pbxproj b/Cache.xcodeproj/project.pbxproj index 4004aeb..1403ff6 100644 --- a/Cache.xcodeproj/project.pbxproj +++ b/Cache.xcodeproj/project.pbxproj @@ -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 = ""; }; + D221E5BF20D00DCC00BC940E /* Entry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = ""; }; + D221E5C320D00DDB00BC940E /* DiskStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskStorage.swift; sourceTree = ""; }; D236F3191F6BEF73004EE01F /* StorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageTests.swift; sourceTree = ""; }; + D270146F20D1018A003B45C7 /* Transformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformer.swift; sourceTree = ""; }; + D270147320D101F3003B45C7 /* StorageAware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageAware.swift; sourceTree = ""; }; + D270147720D1046A003B45C7 /* HybridStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HybridStorage.swift; sourceTree = ""; }; + D270147B20D107DA003B45C7 /* SyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStorage.swift; sourceTree = ""; }; + D270147F20D10982003B45C7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; + D270148320D10E76003B45C7 /* AsyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorage.swift; sourceTree = ""; }; + D270148720D11040003B45C7 /* Storage+Transform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Transform.swift"; sourceTree = ""; }; + D270148B20D113EA003B45C7 /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = ""; }; + D270148F20D1251E003B45C7 /* TypeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeWrapper.swift; sourceTree = ""; }; + D270149320D125AC003B45C7 /* MemoryCapsule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCapsule.swift; sourceTree = ""; }; + D27014A420D129EB003B45C7 /* TransformerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformerFactory.swift; sourceTree = ""; }; D285143E1F6FFE1F00C674D1 /* ObjectConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectConverterTests.swift; sourceTree = ""; }; D28897041F8B79B300C61DEE /* JSONDecoder+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+Extensions.swift"; sourceTree = ""; }; - D292DAF01F6A85F30060F614 /* SyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStorage.swift; sourceTree = ""; }; - D292DAF41F6A94000060F614 /* AsyncStorageAware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorageAware.swift; sourceTree = ""; }; - D292DAF81F6A962D0060F614 /* AsyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorage.swift; sourceTree = ""; }; D292DAFC1F6A970B0060F614 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; D292DB001F6AA06B0060F614 /* SyncStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStorageTests.swift; sourceTree = ""; }; D292DB031F6AA0730060F614 /* AsyncStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStorageTests.swift; sourceTree = ""; }; @@ -163,16 +189,10 @@ D2CF984E1F694FFA00CE8F68 /* MemoryConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryConfig.swift; sourceTree = ""; }; D2CF98501F694FFA00CE8F68 /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; D2CF98531F694FFA00CE8F68 /* DataSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSerializer.swift; sourceTree = ""; }; - D2CF98541F694FFA00CE8F68 /* Entry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Entry.swift; sourceTree = ""; }; D2CF98551F694FFA00CE8F68 /* ExpirationMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpirationMode.swift; sourceTree = ""; }; D2CF98561F694FFA00CE8F68 /* Expiry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expiry.swift; sourceTree = ""; }; D2CF98571F694FFA00CE8F68 /* ImageWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageWrapper.swift; sourceTree = ""; }; D2CF98591F694FFA00CE8F68 /* StorageError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageError.swift; sourceTree = ""; }; - D2CF985B1F694FFA00CE8F68 /* DiskStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskStorage.swift; sourceTree = ""; }; - D2CF985C1F694FFA00CE8F68 /* HybridStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HybridStorage.swift; sourceTree = ""; }; - D2CF985D1F694FFA00CE8F68 /* MemoryStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStorage.swift; sourceTree = ""; }; - D2CF985E1F694FFA00CE8F68 /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; - D2CF985F1F694FFA00CE8F68 /* StorageAware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StorageAware.swift; sourceTree = ""; }; D2CF98721F69513800CE8F68 /* Date+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+ExtensionsTests.swift"; sourceTree = ""; }; D2CF98741F69513800CE8F68 /* MemoryCapsuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCapsuleTests.swift; sourceTree = ""; }; D2CF98751F69513800CE8F68 /* ExpiryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpiryTests.swift; sourceTree = ""; }; @@ -181,9 +201,8 @@ D2CF98791F69513800CE8F68 /* DiskStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskStorageTests.swift; sourceTree = ""; }; D2CF987A1F69513800CE8F68 /* HybridStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HybridStorageTests.swift; sourceTree = ""; }; D2CF987B1F69513800CE8F68 /* MemoryStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStorageTests.swift; sourceTree = ""; }; - D2CF98841F69598900CE8F68 /* TypeWrapperStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeWrapperStorage.swift; sourceTree = ""; }; D2CF98861F695B8F00CE8F68 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; - D2CF98881F695F9400CE8F68 /* TypeWrapperStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeWrapperStorageTests.swift; sourceTree = ""; }; + D2CF98881F695F9400CE8F68 /* StorageSupportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageSupportTests.swift; sourceTree = ""; }; D2D4CC151FA3145000E4A2D5 /* MD5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MD5.swift; sourceTree = ""; }; D2D4CC191FA3166900E4A2D5 /* MD5Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MD5Tests.swift; sourceTree = ""; }; D2D4CC1F1FA3411300E4A2D5 /* JSONDictionaryWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONDictionaryWrapper.swift; sourceTree = ""; }; @@ -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 = ""; @@ -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 = ""; @@ -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; }; diff --git a/Source/Shared/Configuration/MemoryConfig.swift b/Source/Shared/Configuration/MemoryConfig.swift index 62bdfbd..898e603 100644 --- a/Source/Shared/Configuration/MemoryConfig.swift +++ b/Source/Shared/Configuration/MemoryConfig.swift @@ -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 } } diff --git a/Source/Shared/Library/Entry.swift b/Source/Shared/Library/Entry.swift index b4317b7..6ccb7c0 100644 --- a/Source/Shared/Library/Entry.swift +++ b/Source/Shared/Library/Entry.swift @@ -1,7 +1,7 @@ import Foundation /// A wrapper around cached object and its expiry date. -public struct Entry { +public struct Entry { /// Cached object public let object: T /// Expiry date diff --git a/Source/Shared/Library/MemoryCapsule.swift b/Source/Shared/Library/MemoryCapsule.swift new file mode 100644 index 0000000..180c850 --- /dev/null +++ b/Source/Shared/Library/MemoryCapsule.swift @@ -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 + } +} diff --git a/Source/Shared/Library/Optional+Extension.swift b/Source/Shared/Library/Optional+Extension.swift new file mode 100644 index 0000000..69516b2 --- /dev/null +++ b/Source/Shared/Library/Optional+Extension.swift @@ -0,0 +1,11 @@ +import Foundation + +public extension Optional { + func unwrapOrThrow(error: Error) throws -> Wrapped { + if let value = self { + return value + } else { + throw error + } + } +} diff --git a/Source/Shared/Library/StorageError.swift b/Source/Shared/Library/StorageError.swift index 96dcf20..18bd7c1 100644 --- a/Source/Shared/Library/StorageError.swift +++ b/Source/Shared/Library/StorageError.swift @@ -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 } diff --git a/Source/Shared/Library/Transformer.swift b/Source/Shared/Library/Transformer.swift new file mode 100644 index 0000000..71dabad --- /dev/null +++ b/Source/Shared/Library/Transformer.swift @@ -0,0 +1,11 @@ +import Foundation + +public class Transformer { + 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 + } +} diff --git a/Source/Shared/Library/TransformerFactory.swift b/Source/Shared/Library/TransformerFactory.swift new file mode 100644 index 0000000..d6b7b67 --- /dev/null +++ b/Source/Shared/Library/TransformerFactory.swift @@ -0,0 +1,38 @@ +import Foundation + +public class TransformerFactory { + public static func forData() -> Transformer { + let toData: (Data) throws -> Data = { $0 } + + let fromData: (Data) throws -> Data = { $0 } + + return Transformer(toData: toData, fromData: fromData) + } + + public static func forImage() -> Transformer { + 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(toData: toData, fromData: fromData) + } + + public static func forCodable(ofType: U.Type) -> Transformer { + let toData: (U) throws -> Data = { object in + let wrapper = TypeWrapper(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.self, from: data).object + } + + return Transformer(toData: toData, fromData: fromData) + } +} diff --git a/Source/Shared/Library/TypeWrapper.swift b/Source/Shared/Library/TypeWrapper.swift new file mode 100644 index 0000000..f30ea05 --- /dev/null +++ b/Source/Shared/Library/TypeWrapper.swift @@ -0,0 +1,14 @@ +import Foundation + +/// Used to wrap Codable object +public struct TypeWrapper: Codable { + enum CodingKeys: String, CodingKey { + case object + } + + public let object: T + + public init(object: T) { + self.object = object + } +} diff --git a/Source/Shared/Storage/AsyncStorage.swift b/Source/Shared/Storage/AsyncStorage.swift index 224ace2..dcd5e6b 100644 --- a/Source/Shared/Storage/AsyncStorage.swift +++ b/Source/Shared/Storage/AsyncStorage.swift @@ -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 { + public let innerStorage: HybridStorage public let serialQueue: DispatchQueue - init(storage: StorageAware, serialQueue: DispatchQueue) { - self.internalStorage = storage + public init(storage: HybridStorage, serialQueue: DispatchQueue) { + self.innerStorage = storage self.serialQueue = serialQueue } } -extension AsyncStorage: AsyncStorageAware { - public func entry(ofType type: T.Type, forKey key: String, completion: @escaping (Result>) -> Void) { +extension AsyncStorage { + public func entry(forKey key: String, completion: @escaping (Result>) -> 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(_ 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) -> Void) { + entry(forKey: key, completion: { (result: Result>) in + completion(result.map({ entry in + return entry.object + })) + }) + } + + public func existsObject( + forKey key: String, + completion: @escaping (Result) -> Void) { + object(forKey: key, completion: { (result: Result) in + completion(result.map({ _ in + return true + })) + }) + } +} + +public extension AsyncStorage { + func transform(transformer: Transformer) -> AsyncStorage { + let storage = AsyncStorage( + storage: innerStorage.transform(transformer: transformer), + serialQueue: serialQueue + ) + + return storage + } } diff --git a/Source/Shared/Storage/AsyncStorageAware.swift b/Source/Shared/Storage/AsyncStorageAware.swift deleted file mode 100644 index a20995d..0000000 --- a/Source/Shared/Storage/AsyncStorageAware.swift +++ /dev/null @@ -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(ofType type: T.Type, forKey key: String, completion: @escaping (Result) -> 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(ofType type: T.Type, forKey key: String, completion: @escaping (Result>) -> 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(_ 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(ofType type: T.Type, - forKey key: String, - completion: @escaping (Result) -> 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(ofType type: T.Type, forKey key: String, completion: @escaping (Result) -> Void) { - entry(ofType: type, forKey: key, completion: { (result: Result>) in - completion(result.map({ entry in - return entry.object - })) - }) - } - - func existsObject(ofType type: T.Type, - forKey key: String, - completion: @escaping (Result) -> Void) { - object(ofType: type, forKey: key, completion: { (result: Result) in - completion(result.map({ _ in - return true - })) - }) - } -} diff --git a/Source/Shared/Storage/DiskStorage.swift b/Source/Shared/Storage/DiskStorage.swift index 447da43..b5a9e7f 100644 --- a/Source/Shared/Storage/DiskStorage.swift +++ b/Source/Shared/Storage/DiskStorage.swift @@ -1,54 +1,61 @@ import Foundation /// Save objects to file on disk -final class DiskStorage { +final public class DiskStorage { 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 // MARK: - Initialization - required init(config: DiskConfig, fileManager: FileManager = FileManager.default) throws { + public required init(config: DiskConfig, fileManager: FileManager = FileManager.default, transformer: Transformer) 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(ofType type: T.Type, forKey key: String) throws -> Entry { + public func entry(forKey key: String) throws -> Entry { 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(_ 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(transformer: Transformer) -> DiskStorage { + // swiftlint:disable force_try + let storage = try! DiskStorage( + config: config, + fileManager: fileManager, + transformer: transformer + ) + + return storage + } +} diff --git a/Source/Shared/Storage/HybridStorage.swift b/Source/Shared/Storage/HybridStorage.swift index efd0477..db81770 100644 --- a/Source/Shared/Storage/HybridStorage.swift +++ b/Source/Shared/Storage/HybridStorage.swift @@ -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 { + public let memoryStorage: MemoryStorage + public let diskStorage: DiskStorage - init(memoryStorage: MemoryStorage, diskStorage: DiskStorage) { + public init(memoryStorage: MemoryStorage, diskStorage: DiskStorage) { self.memoryStorage = memoryStorage self.diskStorage = diskStorage } } extension HybridStorage: StorageAware { - func entry(ofType type: T.Type, forKey key: String) throws -> Entry { + public func entry(forKey key: String) throws -> Entry { 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(_ 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(transformer: Transformer) -> HybridStorage { + let storage = HybridStorage( + memoryStorage: memoryStorage.transform(), + diskStorage: diskStorage.transform(transformer: transformer) + ) + + return storage + } +} diff --git a/Source/Shared/Storage/MemoryStorage.swift b/Source/Shared/Storage/MemoryStorage.swift index bb48138..c98655e 100644 --- a/Source/Shared/Storage/MemoryStorage.swift +++ b/Source/Shared/Storage/MemoryStorage.swift @@ -1,24 +1,50 @@ import Foundation -/// Save objects to memory based on NSCache -final class MemoryStorage { - /// Memory cache instance +public class MemoryStorage: StorageAware { fileprivate let cache = NSCache() // Memory cache keys fileprivate var keys = Set() /// 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(ofType type: T.Type, forKey key: String) throws -> Entry { +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 { 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(_ 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() -> MemoryStorage { + let storage = MemoryStorage(config: config) + return storage } } diff --git a/Source/Shared/Storage/Storage+Transform.swift b/Source/Shared/Storage/Storage+Transform.swift new file mode 100644 index 0000000..d628fd8 --- /dev/null +++ b/Source/Shared/Storage/Storage+Transform.swift @@ -0,0 +1,18 @@ +import Foundation + +public extension Storage { + func transformData() -> Storage { + let storage = transform(transformer: TransformerFactory.forData()) + return storage + } + + func transformImage() -> Storage { + let storage = transform(transformer: TransformerFactory.forImage()) + return storage + } + + func transformCodable(ofType: U.Type) -> Storage { + let storage = transform(transformer: TransformerFactory.forCodable(ofType: U.self)) + return storage + } +} diff --git a/Source/Shared/Storage/Storage.swift b/Source/Shared/Storage/Storage.swift index 1d71a08..2f74492 100644 --- a/Source/Shared/Storage/Storage.swift +++ b/Source/Shared/Storage/Storage.swift @@ -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 { /// Used for sync operations - fileprivate let sync: StorageAware - - /// Storage used internally by both sync and async storages - private let interalStorage: StorageAware + let syncStorage: SyncStorage + let asyncStorage: AsyncStorage /// 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) throws { + let disk = try DiskStorage(config: diskConfig, transformer: transformer) + let memory = MemoryStorage(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, asyncStorage: AsyncStorage) { + 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(ofType type: T.Type, forKey key: String) throws -> Entry { - return try self.sync.entry(ofType: type, forKey: key) + public func entry(forKey key: String) throws -> Entry { + 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(_ 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(transformer: Transformer) -> Storage { + let storage = Storage( + syncStorage: syncStorage.transform(transformer: transformer), + asyncStorage: asyncStorage.transform(transformer: transformer) + ) + return storage } } diff --git a/Source/Shared/Storage/StorageAware.swift b/Source/Shared/Storage/StorageAware.swift index 3096f09..203d699 100644 --- a/Source/Shared/Storage/StorageAware.swift +++ b/Source/Shared/Storage/StorageAware.swift @@ -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(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(ofType type: T.Type, forKey key: String) throws -> Entry + func entry(forKey key: String) throws -> Entry /** 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(_ 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(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(ofType type: T.Type, forKey key: String) throws -> Bool + */ + func isExpiredObject(forKey key: String) throws -> Bool } public extension StorageAware { - func object(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(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(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 diff --git a/Source/Shared/Storage/SyncStorage.swift b/Source/Shared/Storage/SyncStorage.swift index 30c6867..4b4282a 100644 --- a/Source/Shared/Storage/SyncStorage.swift +++ b/Source/Shared/Storage/SyncStorage.swift @@ -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 { + public let innerStorage: HybridStorage + public let serialQueue: DispatchQueue - init(storage: StorageAware, serialQueue: DispatchQueue) { - self.internalStorage = storage + public init(storage: HybridStorage, serialQueue: DispatchQueue) { + self.innerStorage = storage self.serialQueue = serialQueue } } extension SyncStorage: StorageAware { - public func entry(ofType type: T.Type, forKey key: String) throws -> Entry { + public func entry(forKey key: String) throws -> Entry { var entry: Entry! 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(_ 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(transformer: Transformer) -> SyncStorage { + let storage = SyncStorage( + storage: innerStorage.transform(transformer: transformer), + serialQueue: serialQueue + ) + + return storage + } +} diff --git a/Source/Shared/Storage/TypeWrapperStorage.swift b/Source/Shared/Storage/TypeWrapperStorage.swift deleted file mode 100644 index 26c0717..0000000 --- a/Source/Shared/Storage/TypeWrapperStorage.swift +++ /dev/null @@ -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(ofType type: T.Type, forKey key: String) throws -> Entry { - let wrapperEntry = try internalStorage.entry(ofType: TypeWrapper.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(_ object: T, forKey key: String, - expiry: Expiry? = nil) throws { - let wrapper = TypeWrapper(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: Codable { - enum CodingKeys: String, CodingKey { - case object - } - - let object: T - - init(object: T) { - self.object = object - } -} diff --git a/Tests/iOS/Tests/Library/ObjectConverterTests.swift b/Tests/iOS/Tests/Library/ObjectConverterTests.swift index f6b605b..7354ca8 100644 --- a/Tests/iOS/Tests/Library/ObjectConverterTests.swift +++ b/Tests/iOS/Tests/Library/ObjectConverterTests.swift @@ -2,12 +2,15 @@ import XCTest @testable import Cache final class JSONDecoderExtensionsTests: XCTestCase { - private var storage: HybridStorage! + private var storage: HybridStorage! override func setUp() { super.setUp() - let memory = MemoryStorage(config: MemoryConfig()) - let disk = try! DiskStorage(config: DiskConfig(name: "HybridDisk")) + let memory = MemoryStorage(config: MemoryConfig()) + let disk = try! DiskStorage( + 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") } } diff --git a/Tests/iOS/Tests/Storage/AsyncStorageTests.swift b/Tests/iOS/Tests/Storage/AsyncStorageTests.swift index bcb63f1..b2a181e 100644 --- a/Tests/iOS/Tests/Storage/AsyncStorageTests.swift +++ b/Tests/iOS/Tests/Storage/AsyncStorageTests.swift @@ -3,16 +3,15 @@ import Dispatch @testable import Cache final class AsyncStorageTests: XCTestCase { - private var storage: AsyncStorage! + private var storage: AsyncStorage! 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(config: MemoryConfig()) + let disk = try! DiskStorage(config: DiskConfig(name: "Async Disk"), transformer: TransformerFactory.forCodable(ofType: User.self)) + let hybrid = HybridStorage(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() diff --git a/Tests/iOS/Tests/Storage/DiskStorageTests.swift b/Tests/iOS/Tests/Storage/DiskStorageTests.swift index e7d4253..94a09a1 100644 --- a/Tests/iOS/Tests/Storage/DiskStorageTests.swift +++ b/Tests/iOS/Tests/Storage/DiskStorageTests.swift @@ -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! private let config = DiskConfig(name: "Floppy") override func setUp() { super.setUp() - storage = try! DiskStorage(config: config) + storage = try! DiskStorage(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(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? 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) diff --git a/Tests/iOS/Tests/Storage/HybridStorageTests.swift b/Tests/iOS/Tests/Storage/HybridStorageTests.swift index afa4eb2..f975c21 100644 --- a/Tests/iOS/Tests/Storage/HybridStorageTests.swift +++ b/Tests/iOS/Tests/Storage/HybridStorageTests.swift @@ -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! 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(config: MemoryConfig()) + let disk = try! DiskStorage(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)) } } } diff --git a/Tests/iOS/Tests/Storage/MemoryStorageTests.swift b/Tests/iOS/Tests/Storage/MemoryStorageTests.swift index 34fcc3b..60d8fd4 100644 --- a/Tests/iOS/Tests/Storage/MemoryStorageTests.swift +++ b/Tests/iOS/Tests/Storage/MemoryStorageTests.swift @@ -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! private let config = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10) override func setUp() { super.setUp() - storage = MemoryStorage(config: config) + storage = MemoryStorage(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) diff --git a/Tests/iOS/Tests/Storage/StorageSupportTests.swift b/Tests/iOS/Tests/Storage/StorageSupportTests.swift new file mode 100644 index 0000000..bc328ee --- /dev/null +++ b/Tests/iOS/Tests/Storage/StorageSupportTests.swift @@ -0,0 +1,205 @@ +import XCTest +@testable import Cache + +final class StorageSupportTests: XCTestCase { + private var storage: HybridStorage! + + override func setUp() { + super.setUp() + let memory = MemoryStorage(config: MemoryConfig()) + let disk = try! DiskStorage(config: DiskConfig(name: "PrimitiveDisk"), transformer: TransformerFactory.forCodable(ofType: Bool.self)) + storage = HybridStorage(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.self)) + let set = Set(arrayLiteral: 1, 2, 3) + try s.setObject(set, forKey: "set") + XCTAssertEqual(try s.object(forKey: "set") as Set, 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 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) + } +} diff --git a/Tests/iOS/Tests/Storage/SyncStorageTests.swift b/Tests/iOS/Tests/Storage/SyncStorageTests.swift index 2193237..0934838 100644 --- a/Tests/iOS/Tests/Storage/SyncStorageTests.swift +++ b/Tests/iOS/Tests/Storage/SyncStorageTests.swift @@ -3,14 +3,17 @@ import Dispatch @testable import Cache final class SyncStorageTests: XCTestCase { - private var storage: SyncStorage! + private var storage: SyncStorage! 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(config: MemoryConfig()) + let disk = try! DiskStorage(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")) } } } diff --git a/Tests/iOS/Tests/Storage/TypeWrapperStorageTests.swift b/Tests/iOS/Tests/Storage/TypeWrapperStorageTests.swift deleted file mode 100644 index 5713ac3..0000000 --- a/Tests/iOS/Tests/Storage/TypeWrapperStorageTests.swift +++ /dev/null @@ -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(arrayLiteral: 1, 2, 3) - try storage.setObject(set, forKey: "set") - XCTAssertEqual(try storage.object(ofType: Set.self, forKey: "set") as Set, 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 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) - } - } -}