├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CacheDemo
├── CacheDemo.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── CacheDemo.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── CacheDemo
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── Objects
│ │ ├── MyObject.swift
│ │ └── User.swift
│ ├── ViewController.swift
│ └── dog.jpg
├── CacheDemoTests
│ ├── CacheDemoTests.swift
│ └── Info.plist
├── Podfile
└── Podfile.lock
├── DataCache.podspec
├── LICENSE
├── Package.swift
├── README.md
└── Sources
├── DataCache.swift
├── Dictionary+Cache.swift
└── String+MD5.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 | CacheDemo/Pods
67 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 434929082669DB480039D287 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434929072669DB480039D287 /* User.swift */; };
11 | 47E268892DFE61ABD4B44A78 /* Pods_CacheDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41D6EE2A3522D04F98F4960C /* Pods_CacheDemo.framework */; };
12 | 61C479EF1D2A5E44001D1BE7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C479EE1D2A5E44001D1BE7 /* AppDelegate.swift */; };
13 | 61C479F11D2A5E44001D1BE7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C479F01D2A5E44001D1BE7 /* ViewController.swift */; };
14 | 61C479F41D2A5E44001D1BE7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61C479F21D2A5E44001D1BE7 /* Main.storyboard */; };
15 | 61C479F61D2A5E44001D1BE7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 61C479F51D2A5E44001D1BE7 /* Assets.xcassets */; };
16 | 61C479F91D2A5E44001D1BE7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61C479F71D2A5E44001D1BE7 /* LaunchScreen.storyboard */; };
17 | 61EA9DCB1D33873E0081FA76 /* MyObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61EA9DCA1D33873E0081FA76 /* MyObject.swift */; };
18 | 61EA9DCD1D338E7C0081FA76 /* dog.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 61EA9DCC1D338E7C0081FA76 /* dog.jpg */; };
19 | 61EA9DD91D34D3430081FA76 /* CacheDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61EA9DD81D34D3430081FA76 /* CacheDemoTests.swift */; };
20 | 61ED03531D35F934003C4F5B /* dog.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 61EA9DCC1D338E7C0081FA76 /* dog.jpg */; };
21 | 9E2E7506E81B16D9BC983C69 /* Pods_CacheDemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EABD121933011FE7D1C2531F /* Pods_CacheDemoTests.framework */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | 61EA9DDB1D34D3430081FA76 /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = 61C479E31D2A5E44001D1BE7 /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = 61C479EA1D2A5E44001D1BE7;
30 | remoteInfo = CacheDemo;
31 | };
32 | /* End PBXContainerItemProxy section */
33 |
34 | /* Begin PBXFileReference section */
35 | 1164CDFF8E975F60490D04C5 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; };
36 | 41D6EE2A3522D04F98F4960C /* Pods_CacheDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CacheDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
37 | 434929072669DB480039D287 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
38 | 5C4F486D7057768431A89C14 /* Pods-CacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CacheDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-CacheDemo/Pods-CacheDemo.release.xcconfig"; sourceTree = ""; };
39 | 61C479EB1D2A5E44001D1BE7 /* CacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
40 | 61C479EE1D2A5E44001D1BE7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
41 | 61C479F01D2A5E44001D1BE7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
42 | 61C479F31D2A5E44001D1BE7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
43 | 61C479F51D2A5E44001D1BE7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
44 | 61C479F81D2A5E44001D1BE7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
45 | 61C479FA1D2A5E44001D1BE7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
46 | 61EA9DCA1D33873E0081FA76 /* MyObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyObject.swift; sourceTree = ""; };
47 | 61EA9DCC1D338E7C0081FA76 /* dog.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = dog.jpg; sourceTree = ""; };
48 | 61EA9DD61D34D3430081FA76 /* CacheDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CacheDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
49 | 61EA9DD81D34D3430081FA76 /* CacheDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheDemoTests.swift; sourceTree = ""; };
50 | 61EA9DDA1D34D3430081FA76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | 824315104688EA79283E7383 /* Pods-CacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CacheDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CacheDemo/Pods-CacheDemo.debug.xcconfig"; sourceTree = ""; };
52 | 8591DD7533E64C9CA1A461F0 /* Pods-CacheDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CacheDemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CacheDemoTests/Pods-CacheDemoTests.debug.xcconfig"; sourceTree = ""; };
53 | EABD121933011FE7D1C2531F /* Pods_CacheDemoTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CacheDemoTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54 | F17B922539BD2C93AA98FD93 /* Pods-CacheDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CacheDemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CacheDemoTests/Pods-CacheDemoTests.release.xcconfig"; sourceTree = ""; };
55 | /* End PBXFileReference section */
56 |
57 | /* Begin PBXFrameworksBuildPhase section */
58 | 61C479E81D2A5E44001D1BE7 /* Frameworks */ = {
59 | isa = PBXFrameworksBuildPhase;
60 | buildActionMask = 2147483647;
61 | files = (
62 | 47E268892DFE61ABD4B44A78 /* Pods_CacheDemo.framework in Frameworks */,
63 | );
64 | runOnlyForDeploymentPostprocessing = 0;
65 | };
66 | 61EA9DD31D34D3430081FA76 /* Frameworks */ = {
67 | isa = PBXFrameworksBuildPhase;
68 | buildActionMask = 2147483647;
69 | files = (
70 | 9E2E7506E81B16D9BC983C69 /* Pods_CacheDemoTests.framework in Frameworks */,
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | 10D7070DC3229481FEB4E77C /* Frameworks */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 1164CDFF8E975F60490D04C5 /* Pods.framework */,
81 | 41D6EE2A3522D04F98F4960C /* Pods_CacheDemo.framework */,
82 | EABD121933011FE7D1C2531F /* Pods_CacheDemoTests.framework */,
83 | );
84 | name = Frameworks;
85 | sourceTree = "";
86 | };
87 | 434929062669DB2E0039D287 /* Objects */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 61EA9DCA1D33873E0081FA76 /* MyObject.swift */,
91 | 434929072669DB480039D287 /* User.swift */,
92 | );
93 | path = Objects;
94 | sourceTree = "";
95 | };
96 | 61C479E21D2A5E44001D1BE7 = {
97 | isa = PBXGroup;
98 | children = (
99 | 61C479ED1D2A5E44001D1BE7 /* CacheDemo */,
100 | 61EA9DD71D34D3430081FA76 /* CacheDemoTests */,
101 | 61C479EC1D2A5E44001D1BE7 /* Products */,
102 | 10D7070DC3229481FEB4E77C /* Frameworks */,
103 | E247623DCEF5953FC9C04767 /* Pods */,
104 | );
105 | sourceTree = "";
106 | };
107 | 61C479EC1D2A5E44001D1BE7 /* Products */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 61C479EB1D2A5E44001D1BE7 /* CacheDemo.app */,
111 | 61EA9DD61D34D3430081FA76 /* CacheDemoTests.xctest */,
112 | );
113 | name = Products;
114 | sourceTree = "";
115 | };
116 | 61C479ED1D2A5E44001D1BE7 /* CacheDemo */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 434929062669DB2E0039D287 /* Objects */,
120 | 61C479EE1D2A5E44001D1BE7 /* AppDelegate.swift */,
121 | 61C479F01D2A5E44001D1BE7 /* ViewController.swift */,
122 | 61C479F21D2A5E44001D1BE7 /* Main.storyboard */,
123 | 61C479F51D2A5E44001D1BE7 /* Assets.xcassets */,
124 | 61C479F71D2A5E44001D1BE7 /* LaunchScreen.storyboard */,
125 | 61C479FA1D2A5E44001D1BE7 /* Info.plist */,
126 | 61EA9DCC1D338E7C0081FA76 /* dog.jpg */,
127 | );
128 | path = CacheDemo;
129 | sourceTree = "";
130 | };
131 | 61EA9DD71D34D3430081FA76 /* CacheDemoTests */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 61EA9DD81D34D3430081FA76 /* CacheDemoTests.swift */,
135 | 61EA9DDA1D34D3430081FA76 /* Info.plist */,
136 | );
137 | path = CacheDemoTests;
138 | sourceTree = "";
139 | };
140 | E247623DCEF5953FC9C04767 /* Pods */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 824315104688EA79283E7383 /* Pods-CacheDemo.debug.xcconfig */,
144 | 5C4F486D7057768431A89C14 /* Pods-CacheDemo.release.xcconfig */,
145 | 8591DD7533E64C9CA1A461F0 /* Pods-CacheDemoTests.debug.xcconfig */,
146 | F17B922539BD2C93AA98FD93 /* Pods-CacheDemoTests.release.xcconfig */,
147 | );
148 | name = Pods;
149 | sourceTree = "";
150 | };
151 | /* End PBXGroup section */
152 |
153 | /* Begin PBXNativeTarget section */
154 | 61C479EA1D2A5E44001D1BE7 /* CacheDemo */ = {
155 | isa = PBXNativeTarget;
156 | buildConfigurationList = 61C479FD1D2A5E44001D1BE7 /* Build configuration list for PBXNativeTarget "CacheDemo" */;
157 | buildPhases = (
158 | 69F7F1220DEF21A79EC14CE6 /* [CP] Check Pods Manifest.lock */,
159 | 61C479E71D2A5E44001D1BE7 /* Sources */,
160 | 61C479E81D2A5E44001D1BE7 /* Frameworks */,
161 | 61C479E91D2A5E44001D1BE7 /* Resources */,
162 | F677EFB54BC2712E55CE3A99 /* [CP] Embed Pods Frameworks */,
163 | );
164 | buildRules = (
165 | );
166 | dependencies = (
167 | );
168 | name = CacheDemo;
169 | productName = CacheDemo;
170 | productReference = 61C479EB1D2A5E44001D1BE7 /* CacheDemo.app */;
171 | productType = "com.apple.product-type.application";
172 | };
173 | 61EA9DD51D34D3430081FA76 /* CacheDemoTests */ = {
174 | isa = PBXNativeTarget;
175 | buildConfigurationList = 61EA9DDD1D34D3430081FA76 /* Build configuration list for PBXNativeTarget "CacheDemoTests" */;
176 | buildPhases = (
177 | 244BBDA97B6CC72E66D9B7E4 /* [CP] Check Pods Manifest.lock */,
178 | 61EA9DD21D34D3430081FA76 /* Sources */,
179 | 61EA9DD31D34D3430081FA76 /* Frameworks */,
180 | 61EA9DD41D34D3430081FA76 /* Resources */,
181 | 4A557722DD2C74AB69EF9897 /* [CP] Embed Pods Frameworks */,
182 | );
183 | buildRules = (
184 | );
185 | dependencies = (
186 | 61EA9DDC1D34D3430081FA76 /* PBXTargetDependency */,
187 | );
188 | name = CacheDemoTests;
189 | productName = CacheDemoTests;
190 | productReference = 61EA9DD61D34D3430081FA76 /* CacheDemoTests.xctest */;
191 | productType = "com.apple.product-type.bundle.unit-test";
192 | };
193 | /* End PBXNativeTarget section */
194 |
195 | /* Begin PBXProject section */
196 | 61C479E31D2A5E44001D1BE7 /* Project object */ = {
197 | isa = PBXProject;
198 | attributes = {
199 | LastSwiftUpdateCheck = 0730;
200 | LastUpgradeCheck = 1020;
201 | ORGANIZATIONNAME = "Nguyen Cong Huy";
202 | TargetAttributes = {
203 | 61C479EA1D2A5E44001D1BE7 = {
204 | CreatedOnToolsVersion = 7.3;
205 | LastSwiftMigration = 1000;
206 | ProvisioningStyle = Manual;
207 | };
208 | 61EA9DD51D34D3430081FA76 = {
209 | CreatedOnToolsVersion = 7.3;
210 | LastSwiftMigration = 1000;
211 | TestTargetID = 61C479EA1D2A5E44001D1BE7;
212 | };
213 | };
214 | };
215 | buildConfigurationList = 61C479E61D2A5E44001D1BE7 /* Build configuration list for PBXProject "CacheDemo" */;
216 | compatibilityVersion = "Xcode 3.2";
217 | developmentRegion = English;
218 | hasScannedForEncodings = 0;
219 | knownRegions = (
220 | English,
221 | en,
222 | Base,
223 | );
224 | mainGroup = 61C479E21D2A5E44001D1BE7;
225 | productRefGroup = 61C479EC1D2A5E44001D1BE7 /* Products */;
226 | projectDirPath = "";
227 | projectRoot = "";
228 | targets = (
229 | 61C479EA1D2A5E44001D1BE7 /* CacheDemo */,
230 | 61EA9DD51D34D3430081FA76 /* CacheDemoTests */,
231 | );
232 | };
233 | /* End PBXProject section */
234 |
235 | /* Begin PBXResourcesBuildPhase section */
236 | 61C479E91D2A5E44001D1BE7 /* Resources */ = {
237 | isa = PBXResourcesBuildPhase;
238 | buildActionMask = 2147483647;
239 | files = (
240 | 61EA9DCD1D338E7C0081FA76 /* dog.jpg in Resources */,
241 | 61C479F91D2A5E44001D1BE7 /* LaunchScreen.storyboard in Resources */,
242 | 61C479F61D2A5E44001D1BE7 /* Assets.xcassets in Resources */,
243 | 61C479F41D2A5E44001D1BE7 /* Main.storyboard in Resources */,
244 | );
245 | runOnlyForDeploymentPostprocessing = 0;
246 | };
247 | 61EA9DD41D34D3430081FA76 /* Resources */ = {
248 | isa = PBXResourcesBuildPhase;
249 | buildActionMask = 2147483647;
250 | files = (
251 | 61ED03531D35F934003C4F5B /* dog.jpg in Resources */,
252 | );
253 | runOnlyForDeploymentPostprocessing = 0;
254 | };
255 | /* End PBXResourcesBuildPhase section */
256 |
257 | /* Begin PBXShellScriptBuildPhase section */
258 | 244BBDA97B6CC72E66D9B7E4 /* [CP] Check Pods Manifest.lock */ = {
259 | isa = PBXShellScriptBuildPhase;
260 | buildActionMask = 2147483647;
261 | files = (
262 | );
263 | inputPaths = (
264 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
265 | "${PODS_ROOT}/Manifest.lock",
266 | );
267 | name = "[CP] Check Pods Manifest.lock";
268 | outputPaths = (
269 | "$(DERIVED_FILE_DIR)/Pods-CacheDemoTests-checkManifestLockResult.txt",
270 | );
271 | runOnlyForDeploymentPostprocessing = 0;
272 | shellPath = /bin/sh;
273 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
274 | showEnvVarsInLog = 0;
275 | };
276 | 4A557722DD2C74AB69EF9897 /* [CP] Embed Pods Frameworks */ = {
277 | isa = PBXShellScriptBuildPhase;
278 | buildActionMask = 2147483647;
279 | files = (
280 | );
281 | inputPaths = (
282 | "${PODS_ROOT}/Target Support Files/Pods-CacheDemoTests/Pods-CacheDemoTests-frameworks.sh",
283 | "${BUILT_PRODUCTS_DIR}/DataCache/DataCache.framework",
284 | );
285 | name = "[CP] Embed Pods Frameworks";
286 | outputPaths = (
287 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DataCache.framework",
288 | );
289 | runOnlyForDeploymentPostprocessing = 0;
290 | shellPath = /bin/sh;
291 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CacheDemoTests/Pods-CacheDemoTests-frameworks.sh\"\n";
292 | showEnvVarsInLog = 0;
293 | };
294 | 69F7F1220DEF21A79EC14CE6 /* [CP] Check Pods Manifest.lock */ = {
295 | isa = PBXShellScriptBuildPhase;
296 | buildActionMask = 2147483647;
297 | files = (
298 | );
299 | inputPaths = (
300 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
301 | "${PODS_ROOT}/Manifest.lock",
302 | );
303 | name = "[CP] Check Pods Manifest.lock";
304 | outputPaths = (
305 | "$(DERIVED_FILE_DIR)/Pods-CacheDemo-checkManifestLockResult.txt",
306 | );
307 | runOnlyForDeploymentPostprocessing = 0;
308 | shellPath = /bin/sh;
309 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
310 | showEnvVarsInLog = 0;
311 | };
312 | F677EFB54BC2712E55CE3A99 /* [CP] Embed Pods Frameworks */ = {
313 | isa = PBXShellScriptBuildPhase;
314 | buildActionMask = 2147483647;
315 | files = (
316 | );
317 | inputPaths = (
318 | "${PODS_ROOT}/Target Support Files/Pods-CacheDemo/Pods-CacheDemo-frameworks.sh",
319 | "${BUILT_PRODUCTS_DIR}/DataCache/DataCache.framework",
320 | );
321 | name = "[CP] Embed Pods Frameworks";
322 | outputPaths = (
323 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DataCache.framework",
324 | );
325 | runOnlyForDeploymentPostprocessing = 0;
326 | shellPath = /bin/sh;
327 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CacheDemo/Pods-CacheDemo-frameworks.sh\"\n";
328 | showEnvVarsInLog = 0;
329 | };
330 | /* End PBXShellScriptBuildPhase section */
331 |
332 | /* Begin PBXSourcesBuildPhase section */
333 | 61C479E71D2A5E44001D1BE7 /* Sources */ = {
334 | isa = PBXSourcesBuildPhase;
335 | buildActionMask = 2147483647;
336 | files = (
337 | 434929082669DB480039D287 /* User.swift in Sources */,
338 | 61C479F11D2A5E44001D1BE7 /* ViewController.swift in Sources */,
339 | 61EA9DCB1D33873E0081FA76 /* MyObject.swift in Sources */,
340 | 61C479EF1D2A5E44001D1BE7 /* AppDelegate.swift in Sources */,
341 | );
342 | runOnlyForDeploymentPostprocessing = 0;
343 | };
344 | 61EA9DD21D34D3430081FA76 /* Sources */ = {
345 | isa = PBXSourcesBuildPhase;
346 | buildActionMask = 2147483647;
347 | files = (
348 | 61EA9DD91D34D3430081FA76 /* CacheDemoTests.swift in Sources */,
349 | );
350 | runOnlyForDeploymentPostprocessing = 0;
351 | };
352 | /* End PBXSourcesBuildPhase section */
353 |
354 | /* Begin PBXTargetDependency section */
355 | 61EA9DDC1D34D3430081FA76 /* PBXTargetDependency */ = {
356 | isa = PBXTargetDependency;
357 | target = 61C479EA1D2A5E44001D1BE7 /* CacheDemo */;
358 | targetProxy = 61EA9DDB1D34D3430081FA76 /* PBXContainerItemProxy */;
359 | };
360 | /* End PBXTargetDependency section */
361 |
362 | /* Begin PBXVariantGroup section */
363 | 61C479F21D2A5E44001D1BE7 /* Main.storyboard */ = {
364 | isa = PBXVariantGroup;
365 | children = (
366 | 61C479F31D2A5E44001D1BE7 /* Base */,
367 | );
368 | name = Main.storyboard;
369 | sourceTree = "";
370 | };
371 | 61C479F71D2A5E44001D1BE7 /* LaunchScreen.storyboard */ = {
372 | isa = PBXVariantGroup;
373 | children = (
374 | 61C479F81D2A5E44001D1BE7 /* Base */,
375 | );
376 | name = LaunchScreen.storyboard;
377 | sourceTree = "";
378 | };
379 | /* End PBXVariantGroup section */
380 |
381 | /* Begin XCBuildConfiguration section */
382 | 61C479FB1D2A5E44001D1BE7 /* Debug */ = {
383 | isa = XCBuildConfiguration;
384 | buildSettings = {
385 | ALWAYS_SEARCH_USER_PATHS = NO;
386 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
387 | CLANG_ANALYZER_NONNULL = YES;
388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
389 | CLANG_CXX_LIBRARY = "libc++";
390 | CLANG_ENABLE_MODULES = YES;
391 | CLANG_ENABLE_OBJC_ARC = YES;
392 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
393 | CLANG_WARN_BOOL_CONVERSION = YES;
394 | CLANG_WARN_COMMA = YES;
395 | CLANG_WARN_CONSTANT_CONVERSION = YES;
396 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
397 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
398 | CLANG_WARN_EMPTY_BODY = YES;
399 | CLANG_WARN_ENUM_CONVERSION = YES;
400 | CLANG_WARN_INFINITE_RECURSION = YES;
401 | CLANG_WARN_INT_CONVERSION = YES;
402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
406 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
407 | CLANG_WARN_STRICT_PROTOTYPES = YES;
408 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
409 | CLANG_WARN_UNREACHABLE_CODE = YES;
410 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
412 | COPY_PHASE_STRIP = NO;
413 | DEBUG_INFORMATION_FORMAT = dwarf;
414 | ENABLE_STRICT_OBJC_MSGSEND = YES;
415 | ENABLE_TESTABILITY = YES;
416 | GCC_C_LANGUAGE_STANDARD = gnu99;
417 | GCC_DYNAMIC_NO_PIC = NO;
418 | GCC_NO_COMMON_BLOCKS = YES;
419 | GCC_OPTIMIZATION_LEVEL = 0;
420 | GCC_PREPROCESSOR_DEFINITIONS = (
421 | "DEBUG=1",
422 | "$(inherited)",
423 | );
424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
426 | GCC_WARN_UNDECLARED_SELECTOR = YES;
427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
428 | GCC_WARN_UNUSED_FUNCTION = YES;
429 | GCC_WARN_UNUSED_VARIABLE = YES;
430 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
431 | MTL_ENABLE_DEBUG_INFO = YES;
432 | ONLY_ACTIVE_ARCH = YES;
433 | SDKROOT = iphoneos;
434 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
435 | SWIFT_VERSION = 5.0;
436 | TARGETED_DEVICE_FAMILY = "1,2";
437 | };
438 | name = Debug;
439 | };
440 | 61C479FC1D2A5E44001D1BE7 /* Release */ = {
441 | isa = XCBuildConfiguration;
442 | buildSettings = {
443 | ALWAYS_SEARCH_USER_PATHS = NO;
444 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
445 | CLANG_ANALYZER_NONNULL = YES;
446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
447 | CLANG_CXX_LIBRARY = "libc++";
448 | CLANG_ENABLE_MODULES = YES;
449 | CLANG_ENABLE_OBJC_ARC = YES;
450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
451 | CLANG_WARN_BOOL_CONVERSION = YES;
452 | CLANG_WARN_COMMA = YES;
453 | CLANG_WARN_CONSTANT_CONVERSION = YES;
454 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
456 | CLANG_WARN_EMPTY_BODY = YES;
457 | CLANG_WARN_ENUM_CONVERSION = YES;
458 | CLANG_WARN_INFINITE_RECURSION = YES;
459 | CLANG_WARN_INT_CONVERSION = YES;
460 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
461 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
462 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
463 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
464 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
465 | CLANG_WARN_STRICT_PROTOTYPES = YES;
466 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
467 | CLANG_WARN_UNREACHABLE_CODE = YES;
468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
469 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
470 | COPY_PHASE_STRIP = NO;
471 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
472 | ENABLE_NS_ASSERTIONS = NO;
473 | ENABLE_STRICT_OBJC_MSGSEND = YES;
474 | GCC_C_LANGUAGE_STANDARD = gnu99;
475 | GCC_NO_COMMON_BLOCKS = YES;
476 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
477 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
478 | GCC_WARN_UNDECLARED_SELECTOR = YES;
479 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
480 | GCC_WARN_UNUSED_FUNCTION = YES;
481 | GCC_WARN_UNUSED_VARIABLE = YES;
482 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
483 | MTL_ENABLE_DEBUG_INFO = NO;
484 | SDKROOT = iphoneos;
485 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
486 | SWIFT_VERSION = 5.0;
487 | TARGETED_DEVICE_FAMILY = "1,2";
488 | VALIDATE_PRODUCT = YES;
489 | };
490 | name = Release;
491 | };
492 | 61C479FE1D2A5E44001D1BE7 /* Debug */ = {
493 | isa = XCBuildConfiguration;
494 | baseConfigurationReference = 824315104688EA79283E7383 /* Pods-CacheDemo.debug.xcconfig */;
495 | buildSettings = {
496 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
497 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
498 | CODE_SIGN_STYLE = Manual;
499 | DEVELOPMENT_TEAM = "";
500 | INFOPLIST_FILE = CacheDemo/Info.plist;
501 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
502 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
503 | PRODUCT_BUNDLE_IDENTIFIER = com.nch.CacheDemo;
504 | PRODUCT_NAME = "$(TARGET_NAME)";
505 | PROVISIONING_PROFILE_SPECIFIER = "";
506 | SWIFT_VERSION = 5.0;
507 | };
508 | name = Debug;
509 | };
510 | 61C479FF1D2A5E44001D1BE7 /* Release */ = {
511 | isa = XCBuildConfiguration;
512 | baseConfigurationReference = 5C4F486D7057768431A89C14 /* Pods-CacheDemo.release.xcconfig */;
513 | buildSettings = {
514 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
515 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
516 | CODE_SIGN_STYLE = Manual;
517 | DEVELOPMENT_TEAM = "";
518 | INFOPLIST_FILE = CacheDemo/Info.plist;
519 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
520 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
521 | PRODUCT_BUNDLE_IDENTIFIER = com.nch.CacheDemo;
522 | PRODUCT_NAME = "$(TARGET_NAME)";
523 | PROVISIONING_PROFILE_SPECIFIER = "";
524 | SWIFT_VERSION = 5.0;
525 | };
526 | name = Release;
527 | };
528 | 61EA9DDE1D34D3430081FA76 /* Debug */ = {
529 | isa = XCBuildConfiguration;
530 | baseConfigurationReference = 8591DD7533E64C9CA1A461F0 /* Pods-CacheDemoTests.debug.xcconfig */;
531 | buildSettings = {
532 | BUNDLE_LOADER = "$(TEST_HOST)";
533 | INFOPLIST_FILE = CacheDemoTests/Info.plist;
534 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
535 | PRODUCT_BUNDLE_IDENTIFIER = com.nch.CacheDemoTests;
536 | PRODUCT_NAME = "$(TARGET_NAME)";
537 | SWIFT_VERSION = 5.0;
538 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CacheDemo.app/CacheDemo";
539 | };
540 | name = Debug;
541 | };
542 | 61EA9DDF1D34D3430081FA76 /* Release */ = {
543 | isa = XCBuildConfiguration;
544 | baseConfigurationReference = F17B922539BD2C93AA98FD93 /* Pods-CacheDemoTests.release.xcconfig */;
545 | buildSettings = {
546 | BUNDLE_LOADER = "$(TEST_HOST)";
547 | INFOPLIST_FILE = CacheDemoTests/Info.plist;
548 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
549 | PRODUCT_BUNDLE_IDENTIFIER = com.nch.CacheDemoTests;
550 | PRODUCT_NAME = "$(TARGET_NAME)";
551 | SWIFT_VERSION = 5.0;
552 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CacheDemo.app/CacheDemo";
553 | };
554 | name = Release;
555 | };
556 | /* End XCBuildConfiguration section */
557 |
558 | /* Begin XCConfigurationList section */
559 | 61C479E61D2A5E44001D1BE7 /* Build configuration list for PBXProject "CacheDemo" */ = {
560 | isa = XCConfigurationList;
561 | buildConfigurations = (
562 | 61C479FB1D2A5E44001D1BE7 /* Debug */,
563 | 61C479FC1D2A5E44001D1BE7 /* Release */,
564 | );
565 | defaultConfigurationIsVisible = 0;
566 | defaultConfigurationName = Release;
567 | };
568 | 61C479FD1D2A5E44001D1BE7 /* Build configuration list for PBXNativeTarget "CacheDemo" */ = {
569 | isa = XCConfigurationList;
570 | buildConfigurations = (
571 | 61C479FE1D2A5E44001D1BE7 /* Debug */,
572 | 61C479FF1D2A5E44001D1BE7 /* Release */,
573 | );
574 | defaultConfigurationIsVisible = 0;
575 | defaultConfigurationName = Release;
576 | };
577 | 61EA9DDD1D34D3430081FA76 /* Build configuration list for PBXNativeTarget "CacheDemoTests" */ = {
578 | isa = XCConfigurationList;
579 | buildConfigurations = (
580 | 61EA9DDE1D34D3430081FA76 /* Debug */,
581 | 61EA9DDF1D34D3430081FA76 /* Release */,
582 | );
583 | defaultConfigurationIsVisible = 0;
584 | defaultConfigurationName = Release;
585 | };
586 | /* End XCConfigurationList section */
587 | };
588 | rootObject = 61C479E31D2A5E44001D1BE7 /* Project object */;
589 | }
590 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // CacheDemo
4 | //
5 | // Created by Nguyen Cong Huy on 7/4/16.
6 | // Copyright © 2016 Nguyen Cong Huy. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | NSAppTransportSecurity
26 |
27 | NSAllowsArbitraryLoads
28 |
29 |
30 | UILaunchStoryboardName
31 | LaunchScreen
32 | UIMainStoryboardFile
33 | Main
34 | UIRequiredDeviceCapabilities
35 |
36 | armv7
37 |
38 | UISupportedInterfaceOrientations
39 |
40 | UIInterfaceOrientationPortrait
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/Objects/MyObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyObject.swift
3 | // CacheDemo
4 | //
5 | // Created by Nguyen Cong Huy on 7/11/16.
6 | // Copyright © 2016 Nguyen Cong Huy. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class MyObject: NSObject, NSCoding {
12 | open var name = ""
13 | open var yearOld = 0
14 |
15 | override init() {
16 |
17 | }
18 |
19 | public required init?(coder aDecoder: NSCoder) {
20 | self.name = aDecoder.decodeObject(forKey: "name") as! String
21 | self.yearOld = aDecoder.decodeInteger(forKey: "yearOld")
22 | }
23 |
24 | open func encode(with aCoder: NSCoder) {
25 | aCoder.encode(self.name, forKey: "name")
26 | aCoder.encode(self.yearOld, forKey: "yearOld")
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/Objects/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // CacheDemo
4 | //
5 | // Created by Nguyen Cong Huy on 04/06/2021.
6 | // Copyright © 2021 Nguyen Cong Huy. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct User: Codable, Equatable {
12 | let name: String
13 | let yearOld: Double
14 | }
15 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // CacheDemo
4 | //
5 | // Created by Nguyen Cong Huy on 7/4/16.
6 | // Copyright © 2016 Nguyen Cong Huy. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import DataCache
11 |
12 | class ViewController: UIViewController {
13 |
14 | static let imageKey = "imageKey"
15 |
16 | @IBOutlet weak var keyTextField: UITextField!
17 | @IBOutlet weak var valueTextField: UITextField!
18 | @IBOutlet weak var imageView: UIImageView!
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | }
23 |
24 | @IBAction func readButtonTouched(_ sender: AnyObject) {
25 | let key = keyTextField.text!
26 | let cachedString = DataCache.instance.readString(forKey: key)
27 | valueTextField.text = cachedString
28 | }
29 |
30 | @IBAction func writeButtonTouched(_ sender: AnyObject) {
31 | let string = valueTextField.text!
32 | let key = keyTextField.text!
33 | DataCache.instance.write(string: string, forKey: key)
34 | }
35 |
36 | @IBAction func cleanButtonTapped(_ sender: AnyObject) {
37 | let key = keyTextField.text!
38 | DataCache.instance.clean(byKey: key)
39 | }
40 |
41 | @IBAction func cleanAllButtonTapped(_ sender: AnyObject) {
42 | DataCache.instance.cleanAll()
43 | }
44 |
45 | func writeImageToCache() {
46 | let image = UIImage(named: "dog.jpg")
47 | DataCache.instance.write(image: image!, forKey: ViewController.imageKey)
48 | }
49 |
50 | func readImageFromCacheAndShow() {
51 | let image = DataCache.instance.readImage(forKey: ViewController.imageKey)
52 | imageView.image = image
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemo/dog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huynguyencong/DataCache/bcb54d313a6ab16fc3f8fa5884ec0f5f331c6ae3/CacheDemo/CacheDemo/dog.jpg
--------------------------------------------------------------------------------
/CacheDemo/CacheDemoTests/CacheDemoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CacheDemoTests.swift
3 | // CacheDemoTests
4 | //
5 | // Created by Nguyen Cong Huy on 7/12/16.
6 | // Copyright © 2016 Nguyen Cong Huy. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import DataCache
11 | @testable import CacheDemo
12 |
13 | class CacheDemoTests: XCTestCase {
14 |
15 | override func setUp() {
16 | super.setUp()
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testReadWriteCache() {
25 | let str = "testReadWriteCache"
26 | let key = "testReadWriteCacheKey"
27 |
28 | DataCache.instance.write(object:str as NSCoding, forKey: key)
29 | let cachedString = DataCache.instance.readString(forKey: key)
30 |
31 | XCTAssert(cachedString == str)
32 | }
33 |
34 | func testReadWriteCodable() {
35 | let user = User(name: "Minh", yearOld: 20)
36 | let key = "testReadWriteCodableKey"
37 |
38 | do {
39 | try DataCache.instance.write(codable: user, forKey: key)
40 | let readUser: User? = try DataCache.instance.readCodable(forKey: key)
41 | XCTAssert(readUser == user)
42 | } catch {
43 | assertionFailure(error.localizedDescription)
44 | }
45 | }
46 |
47 | func testWriteCacheToDisk() {
48 | let str = "testWriteCacheToDisk"
49 | let key = "testWriteCacheToDiskKey"
50 |
51 | let expectation = self.expectation(description: "Write to disk is an asynchonous operation")
52 |
53 | DataCache.instance.write(object: str as NSCoding, forKey: key)
54 | DataCache.instance.cleanMemCache()
55 |
56 | // wait for write to disk successful
57 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double((Int64)(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
58 | let cachedString = DataCache.instance.readString(forKey: key)
59 | XCTAssert(cachedString == str)
60 | expectation.fulfill()
61 | }
62 |
63 | waitForExpectations(timeout: 2) { (error) in
64 | if let error = error {
65 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
66 | }
67 | }
68 | }
69 |
70 | func testReadWriteImage() {
71 | let image = UIImage(named: "dog.jpg")
72 | let key = "testReadWriteImageKey"
73 |
74 | DataCache.instance.write(image: image!, forKey: key)
75 | let cachedImage = DataCache.instance.readImageForKey(key: key)
76 |
77 | if let image = image, let cachedImage = cachedImage {
78 | XCTAssert(image.size == cachedImage.size)
79 | }
80 | else {
81 | XCTFail()
82 | }
83 | }
84 |
85 | func testHasDataOnDiskForKey() {
86 | let str = "testHasDataOnDiskForKey"
87 | let key = "testHasDataOnDiskForKeyKey"
88 | let expectation = self.expectation(description: "Write to disk is an asynchonous operation")
89 |
90 | DataCache.instance.write(object: str as NSCoding, forKey: key)
91 |
92 | // wait for write to disk successful
93 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double((Int64)(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
94 | let hasDataOnDisk = DataCache.instance.hasDataOnDisk(forKey: key)
95 | XCTAssert(hasDataOnDisk == true)
96 | expectation.fulfill()
97 | }
98 |
99 | waitForExpectations(timeout: 2) { (error) in
100 | if let error = error {
101 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
102 | }
103 | }
104 | }
105 |
106 | func testHasDataOnMemForKey() {
107 | let str = "testHasDataOnMemForKey"
108 | let key = "testHasDataOnMemForKeyKey"
109 |
110 | DataCache.instance.write(object: str as NSCoding, forKey: key)
111 | let hasDataOnMem = DataCache.instance.hasDataOnMem(forKey: key)
112 |
113 | XCTAssert(hasDataOnMem == true)
114 | }
115 |
116 | func testCleanCache() {
117 | let str = "testCleanCache"
118 | let key = "testCleanCacheKey"
119 | let expectation = self.expectation(description: "Clean is an asynchonous operation")
120 |
121 | DataCache.instance.write(object: str as NSCoding, forKey: key)
122 |
123 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double((Int64)(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
124 | DataCache.instance.cleanAll()
125 |
126 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double((Int64)(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
127 | let cachedString = DataCache.instance.readString(forKey: key)
128 | XCTAssert(cachedString == nil)
129 | expectation.fulfill()
130 | }
131 | }
132 |
133 | waitForExpectations(timeout: 3) { (error) in
134 | if let error = error {
135 | XCTFail("waitForExpectationsWithTimeout errored: \(error)")
136 | }
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/CacheDemo/CacheDemoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CacheDemo/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, "9.0"
2 |
3 | use_frameworks!
4 |
5 | def commonpods
6 | pod 'DataCache', :path => '../'
7 | end
8 |
9 |
10 | target 'CacheDemo' do
11 | commonpods
12 | end
13 |
14 | target 'CacheDemoTests' do
15 | commonpods
16 | end
17 |
--------------------------------------------------------------------------------
/CacheDemo/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - DataCache (1.4)
3 |
4 | DEPENDENCIES:
5 | - DataCache (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | DataCache:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | DataCache: 58345b1a6c12fed9919fe4c00716ede7bc7e8142
13 |
14 | PODFILE CHECKSUM: 173aa6faeefae7d64a11c9fa0c3b367ba970633a
15 |
16 | COCOAPODS: 1.6.1
17 |
--------------------------------------------------------------------------------
/DataCache.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'DataCache'
3 | s.version = '1.7'
4 | s.summary = 'Simplest way to cache data on memory and disk'
5 | s.homepage = 'https://github.com/huynguyencong/DataCache'
6 | s.license = { :type => 'MIT', :file => 'LICENSE' }
7 | s.source = { :git => 'https://github.com/huynguyencong/DataCache.git', :tag => "#{s.version}" }
8 | s.author = { 'Huy Nguyen Cong' => 'https://github.com/huynguyencong' }
9 | s.osx.deployment_target = '10.11'
10 | s.ios.deployment_target = '13.0'
11 | s.source_files = 'Sources/*.{swift}'
12 | s.requires_arc = true
13 | s.swift_versions = ['5.7.1']
14 | end
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Huy Nguyen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "DataCache",
8 | platforms: [
9 | .macOS(.v10_11),
10 | .iOS(.v13)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, and make them visible to other packages.
14 | .library(
15 | name: "DataCache",
16 | targets: ["DataCache"]),
17 | ],
18 | dependencies: [
19 | // Dependencies declare other packages that this package depends on.
20 | // .package(url: /* package url */, from: "1.0.0"),
21 | ],
22 | targets: [
23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
24 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
25 | .target(
26 | name: "DataCache",
27 | path: "Sources")
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cache
2 | This is a simple disk and memory cache for iOS and macOS written in Swift. It can cache `Codable` types, `NSCoding` types, primitive types (`String`, `Int`, `Array`, etc.).
3 |
4 | ## Why would I like to use it?
5 | There are some reasons why you would like to use this libary for caching:
6 |
7 | - Easiest, simplest way to cache, store data in memory and disk.
8 | - Fast response time. Instead of waiting for data loading from Internet, now you can load it from cache before update it from remote resources.
9 | - Loading data from cache, just update from remote source when the cache expired, will save user's Internet data (especially mobile data) and help to improve battery life.
10 | - It stores data on disk and memory. When you read cache, it will try to get data from memory first. That makes reading speed fast. It cleans memory cache when RAM is full automatically, so it doesn't make your application out of memory.
11 |
12 | ## Compatibility
13 | - iOS 9/macOS 10.11 and later (if you want to use it on iOS 7, you can add files manually)
14 | - Swift 5 and later (for earlier Swift version, please use earlier DataCache version)
15 |
16 | ## Usage
17 | ### Installing
18 | #### Cocoapod
19 | Add below lines to your Podfile:
20 |
21 | ```ruby
22 | pod 'DataCache'
23 | ```
24 |
25 | Note: If above pod doesn't work, try using the below pod defination in Podfile:
26 | `pod 'DataCache', :git => 'https://github.com/huynguyencong/DataCache.git'`
27 |
28 | #### Swift Package Manager
29 | In Xcode, select menu File -> Swift Packages -> Add Package Dependency. Select a target, then add this link to the input field:
30 | `https://github.com/huynguyencong/DataCache.git`
31 |
32 | #### Manually
33 | Add all files in the `Sources` folder to your project.
34 |
35 | ### Simple to use
36 | Use default cache a create new cache if you want. In each cache instance, you can setup cache size and expired time.
37 | #### Read and write an object
38 |
39 | Cache `Codable` types, include your custom types that conformed to `Codable` protocol, and primitive types (`String`, `Int`, etc.) which have already conformed to `Codable` by default.
40 |
41 | - Write:
42 | ```swift
43 | do {
44 | try DataCache.instance.write(codable: myCodableObject, forKey: "myKey")
45 | } catch {
46 | print("Write error \(error.localizedDescription)")
47 | }
48 | ```
49 |
50 | - Read:
51 |
52 | ```swift
53 | do {
54 | let object: MyCodableObject? = try DataCache.instance.readCodable(forKey: "myKey")
55 | } catch {
56 | print("Read error \(error.localizedDescription)")
57 | }
58 | ```
59 |
60 | #### Read and write `UIImage` or `NSImage`
61 |
62 | - Write:
63 | ```swift
64 | // on iOS
65 | let image = UIImage(named: "myImageName")
66 | // on macOS
67 | let image = NSImage(named: "myImageName")
68 | // will be written in a same way
69 | DataCache.instance.write(image: image!, forKey: "imageKey")
70 | ```
71 |
72 | - Read:
73 | ```swift
74 | let image = DataCache.instance.readImage(forKey: "imageKey")
75 | ```
76 |
77 | #### Read and write `Data`
78 |
79 | - Write:
80 | ```swift
81 | let data = ... // your data
82 | DataCache.instance.write(data: data, forKey: "myKey")
83 | ```
84 |
85 | - Read:
86 | ```swift
87 | let data = DataCache.instance.readData(forKey: "myKey")
88 | ```
89 |
90 | #### Clean cache
91 |
92 | You can clean by key, or clean all, use one of below methods:
93 | ```swift
94 | DataCache.instance.clean(byKey: "myKey")
95 | DataCache.instance.cleanAll()
96 | ```
97 | It also clear cache after expiration day. The Default expiration day is 1 week. If you want to customize expiration, please create your customized cache by below instruction.
98 |
99 | #### Custom a class for cache ability
100 | Just make your type conform to Codable.
101 |
102 | ```swift
103 | struct User: Codable {
104 | let name: String
105 | let yearOld: Double
106 | }
107 | ```
108 |
109 | #### Create custom Cache instance
110 |
111 | Beside using default cache `DataCache.instance`, you can create your cache instances, then you can set different expiration time, disk size, disk path. The name parameter specifies path name for disk cache.
112 |
113 | ```swift
114 | let cache = DataCache(name: "MyCustomCache")
115 | cache.maxDiskCacheSize = 100*1024*1024 // 100 MB
116 | cache.maxCachePeriodInSecond = 7*86400 // 1 week
117 | ```
118 |
119 | ## License
120 | This open source use some piece of code from Kingfisher library.
121 |
122 | DataCache is released under the MIT license. See LICENSE for details. Copyright © Nguyen Cong Huy
123 |
--------------------------------------------------------------------------------
/Sources/DataCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cache.swift
3 | // CacheDemo
4 | //
5 | // Created by Nguyen Cong Huy on 7/4/16.
6 | // Copyright © 2016 Nguyen Cong Huy. All rights reserved.
7 | //
8 |
9 | #if os(iOS)
10 | import UIKit
11 | #elseif os(OSX)
12 | import AppKit
13 | #endif
14 |
15 | public enum ImageFormat {
16 | case unknown, png, jpeg
17 | }
18 |
19 | open class DataCache {
20 | static let cacheDirectoryPrefix = "com.nch.cache."
21 | static let ioQueuePrefix = "com.nch.queue."
22 | static let defaultMaxCachePeriodInSecond: TimeInterval = 60 * 60 * 24 * 7 // a week
23 |
24 | public static let instance = DataCache(name: "default")
25 |
26 | let cachePath: String
27 |
28 | let memCache = NSCache()
29 | let ioQueue: DispatchQueue
30 | let fileManager: FileManager
31 |
32 | /// Name of cache
33 | open var name: String = ""
34 |
35 | /// Life time of disk cache, in second. Default is a week
36 | open var maxCachePeriodInSecond = DataCache.defaultMaxCachePeriodInSecond
37 |
38 | /// Size is allocated for disk cache, in byte. 0 mean no limit. Default is 0
39 | open var maxDiskCacheSize: UInt = 0
40 |
41 | /// Specify distinc name param, it represents folder name for disk cache
42 | public init(name: String, path: String? = nil) {
43 | self.name = name
44 |
45 | var cachePath = path ?? NSSearchPathForDirectoriesInDomains(.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first!
46 | cachePath = (cachePath as NSString).appendingPathComponent(DataCache.cacheDirectoryPrefix + name)
47 | self.cachePath = cachePath
48 |
49 | ioQueue = DispatchQueue(label: DataCache.ioQueuePrefix + name)
50 |
51 | self.fileManager = FileManager()
52 |
53 | #if !os(OSX) && !os(watchOS)
54 | NotificationCenter.default.addObserver(self, selector: #selector(cleanExpiredDiskCache), name: UIApplication.willTerminateNotification, object: nil)
55 | NotificationCenter.default.addObserver(self, selector: #selector(cleanExpiredDiskCache), name: UIApplication.didEnterBackgroundNotification, object: nil)
56 | #endif
57 | }
58 |
59 | deinit {
60 | NotificationCenter.default.removeObserver(self)
61 | }
62 | }
63 |
64 | // MARK: - Store data
65 |
66 | extension DataCache {
67 |
68 | /// Write data for key. This is an async operation.
69 | public func write(data: Data, forKey key: String) {
70 | memCache.setObject(data as AnyObject, forKey: key as AnyObject)
71 | writeDataToDisk(data: data, key: key)
72 | }
73 |
74 | private func writeDataToDisk(data: Data, key: String) {
75 | ioQueue.async {
76 | if self.fileManager.fileExists(atPath: self.cachePath) == false {
77 | do {
78 | try self.fileManager.createDirectory(atPath: self.cachePath, withIntermediateDirectories: true, attributes: nil)
79 | } catch {
80 | print("DataCache: Error while creating cache folder: \(error.localizedDescription)")
81 | }
82 | }
83 |
84 | self.fileManager.createFile(atPath: self.cachePath(forKey: key), contents: data, attributes: nil)
85 | }
86 | }
87 |
88 | /// Read data for key
89 | public func readData(forKey key:String) -> Data? {
90 | var data = memCache.object(forKey: key as AnyObject) as? Data
91 |
92 | if data == nil {
93 | if let dataFromDisk = readDataFromDisk(forKey: key) {
94 | data = dataFromDisk
95 | memCache.setObject(dataFromDisk as AnyObject, forKey: key as AnyObject)
96 | }
97 | }
98 |
99 | return data
100 | }
101 |
102 | /// Read data from disk for key
103 | public func readDataFromDisk(forKey key: String) -> Data? {
104 | return self.fileManager.contents(atPath: cachePath(forKey: key))
105 | }
106 |
107 | // MARK: - Read & write Codable types
108 | public func write(codable: T, forKey key: String) throws {
109 | let data = try JSONEncoder().encode(codable)
110 | write(data: data, forKey: key)
111 | }
112 |
113 | public func readCodable(forKey key: String) throws -> T? {
114 | guard let data = readData(forKey: key) else { return nil }
115 | return try JSONDecoder().decode(T.self, from: data)
116 | }
117 |
118 | // MARK: - Read & write primitive types
119 |
120 |
121 | /// Write an object for key. This object must inherit from `NSObject` and implement `NSCoding` protocol. `String`, `Array`, `Dictionary` conform to this method.
122 | ///
123 | /// NOTE: Can't write `UIImage` with this method. Please use `writeImage(_:forKey:)` to write an image
124 | public func write(object: NSCoding, forKey key: String) {
125 | let data = NSKeyedArchiver.archivedData(withRootObject: object)
126 | write(data: data, forKey: key)
127 | }
128 |
129 | /// Write a string for key
130 | public func write(string: String, forKey key: String) {
131 | write(object: string as NSCoding, forKey: key)
132 | }
133 |
134 | /// Write a dictionary for key
135 | public func write(dictionary: Dictionary, forKey key: String) {
136 | write(object: dictionary as NSCoding, forKey: key)
137 | }
138 |
139 | /// Write an array for key
140 | public func write(array: Array, forKey key: String) {
141 | write(object: array as NSCoding, forKey: key)
142 | }
143 |
144 | /// Read an object for key. This object must inherit from `NSObject` and implement NSCoding protocol. `String`, `Array`, `Dictionary` conform to this method
145 | public func readObject(forKey key: String) -> NSObject? {
146 | let data = readData(forKey: key)
147 |
148 | if let data = data {
149 | return NSKeyedUnarchiver.unarchiveObject(with: data) as? NSObject
150 | }
151 |
152 | return nil
153 | }
154 |
155 | /// Read a string for key
156 | public func readString(forKey key: String) -> String? {
157 | return readObject(forKey: key) as? String
158 | }
159 |
160 | /// Read an array for key
161 | public func readArray(forKey key: String) -> Array? {
162 | return readObject(forKey: key) as? Array
163 | }
164 |
165 | /// Read a dictionary for key
166 | public func readDictionary(forKey key: String) -> Dictionary? {
167 | return readObject(forKey: key) as? Dictionary
168 | }
169 |
170 | // MARK: - Read & write image
171 |
172 | /// Write image for key. Please use this method to write an image instead of `writeObject(_:forKey:)`
173 | #if os(iOS)
174 | public func write(image: UIImage, forKey key: String, format: ImageFormat? = nil) {
175 | var data: Data? = nil
176 |
177 | if let format = format, format == .png {
178 | data = image.pngData()
179 | }
180 | else {
181 | data = image.jpegData(compressionQuality: 0.9)
182 | }
183 |
184 | if let data = data {
185 | write(data: data, forKey: key)
186 | }
187 | }
188 |
189 | /// Read image for key. Please use this method to write an image instead of `readObject(forKey:)`
190 | public func readImage(forKey key: String) -> UIImage? {
191 | let data = readData(forKey: key)
192 | if let data = data {
193 | return UIImage(data: data, scale: 1.0)
194 | }
195 |
196 | return nil
197 | }
198 |
199 | @available(*, deprecated, message: "Please use `readImage(forKey:)` instead. This will be removed in the future.")
200 | public func readImageForKey(key: String) -> UIImage? {
201 | return readImage(forKey: key)
202 | }
203 | #elseif os(OSX)
204 | public func write(image: NSImage, forKey key: String, format: ImageFormat? = nil) {
205 | var data: Data? = nil
206 |
207 | if let format = format, format == .png {
208 | data = image.pngData
209 | }
210 | else {
211 | data = image.jpegData
212 | }
213 |
214 | if let data = data {
215 | write(data: data, forKey: key)
216 | }
217 | }
218 |
219 | /// Read image for key. Please use this method to write an image instead of `readObject(forKey:)`
220 | public func readImage(forKey key: String) -> NSImage? {
221 | let data = readData(forKey: key)
222 | if let data = data {
223 | return NSImage(data: data)
224 | }
225 |
226 | return nil
227 | }
228 |
229 | @available(*, deprecated, message: "Please use `readImage(forKey:)` instead. This will be removed in the future.")
230 | public func readImageForKey(key: String) -> NSImage? {
231 | return readImage(forKey: key)
232 | }
233 | #endif
234 | }
235 |
236 | // MARK: - Utils
237 |
238 | extension DataCache {
239 | /// Check if has data for key
240 | public func hasData(forKey key: String) -> Bool {
241 | return hasDataOnDisk(forKey: key) || hasDataOnMem(forKey: key)
242 | }
243 |
244 | /// Check if has data on disk
245 | public func hasDataOnDisk(forKey key: String) -> Bool {
246 | return self.fileManager.fileExists(atPath: self.cachePath(forKey: key))
247 | }
248 |
249 | /// Check if has data on mem
250 | public func hasDataOnMem(forKey key: String) -> Bool {
251 | return (memCache.object(forKey: key as AnyObject) != nil)
252 | }
253 | }
254 |
255 | // MARK: - Clean
256 |
257 | extension DataCache {
258 |
259 | /// Clean all mem cache and disk cache. This is an async operation.
260 | public func cleanAll() {
261 | cleanMemCache()
262 | cleanDiskCache()
263 | }
264 |
265 | /// Clean cache by key. This is an async operation.
266 | public func clean(byKey key: String) {
267 | memCache.removeObject(forKey: key as AnyObject)
268 |
269 | ioQueue.async {
270 | do {
271 | try self.fileManager.removeItem(atPath: self.cachePath(forKey: key))
272 | } catch {
273 | print("DataCache: Error while remove file: \(error.localizedDescription)")
274 | }
275 | }
276 | }
277 |
278 | public func cleanMemCache() {
279 | memCache.removeAllObjects()
280 | }
281 |
282 | public func cleanDiskCache() {
283 | ioQueue.async {
284 | do {
285 | try self.fileManager.removeItem(atPath: self.cachePath)
286 | } catch {
287 | print("DataCache: Error when clean disk: \(error.localizedDescription)")
288 | }
289 | }
290 | }
291 |
292 | /// Clean expired disk cache. This is an async operation.
293 | @objc public func cleanExpiredDiskCache() {
294 | cleanExpiredDiskCache(completion: nil)
295 | }
296 |
297 | // This method is from Kingfisher
298 | /**
299 | Clean expired disk cache. This is an async operation.
300 |
301 | - parameter completionHandler: Called after the operation completes.
302 | */
303 | open func cleanExpiredDiskCache(completion handler: (()->())? = nil) {
304 |
305 | // Do things in cocurrent io queue
306 | ioQueue.async {
307 |
308 | var (URLsToDelete, diskCacheSize, cachedFiles) = self.travelCachedFiles(onlyForCacheSize: false)
309 |
310 | for fileURL in URLsToDelete {
311 | do {
312 | try self.fileManager.removeItem(at: fileURL)
313 | } catch {
314 | print("DataCache: Error while removing files \(error.localizedDescription)")
315 | }
316 | }
317 |
318 | if self.maxDiskCacheSize > 0 && diskCacheSize > self.maxDiskCacheSize {
319 | let targetSize = self.maxDiskCacheSize / 2
320 |
321 | // Sort files by last modify date. We want to clean from the oldest files.
322 | let sortedFiles = cachedFiles.keysSortedByValue {
323 | resourceValue1, resourceValue2 -> Bool in
324 |
325 | if let date1 = resourceValue1.contentAccessDate,
326 | let date2 = resourceValue2.contentAccessDate
327 | {
328 | return date1.compare(date2) == .orderedAscending
329 | }
330 |
331 | // Not valid date information. This should not happen. Just in case.
332 | return true
333 | }
334 |
335 | for fileURL in sortedFiles {
336 |
337 | do {
338 | try self.fileManager.removeItem(at: fileURL)
339 | } catch {
340 | print("DataCache: Error while removing files \(error.localizedDescription)")
341 | }
342 |
343 | URLsToDelete.append(fileURL)
344 |
345 | if let fileSize = cachedFiles[fileURL]?.totalFileAllocatedSize {
346 | diskCacheSize -= UInt(fileSize)
347 | }
348 |
349 | if diskCacheSize < targetSize {
350 | break
351 | }
352 | }
353 | }
354 |
355 | DispatchQueue.main.async(execute: { () -> Void in
356 | handler?()
357 | })
358 | }
359 | }
360 | }
361 |
362 | // MARK: - Helpers
363 |
364 | extension DataCache {
365 |
366 | // This method is from Kingfisher
367 | fileprivate func travelCachedFiles(onlyForCacheSize: Bool) -> (urlsToDelete: [URL], diskCacheSize: UInt, cachedFiles: [URL: URLResourceValues]) {
368 |
369 | let diskCacheURL = URL(fileURLWithPath: cachePath)
370 | let resourceKeys: Set = [.isDirectoryKey, .contentAccessDateKey, .totalFileAllocatedSizeKey]
371 | let expiredDate: Date? = (maxCachePeriodInSecond < 0) ? nil : Date(timeIntervalSinceNow: -maxCachePeriodInSecond)
372 |
373 | var cachedFiles = [URL: URLResourceValues]()
374 | var urlsToDelete = [URL]()
375 | var diskCacheSize: UInt = 0
376 |
377 | for fileUrl in (try? fileManager.contentsOfDirectory(at: diskCacheURL, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles)) ?? [] {
378 |
379 | do {
380 | let resourceValues = try fileUrl.resourceValues(forKeys: resourceKeys)
381 | // If it is a Directory. Continue to next file URL.
382 | if resourceValues.isDirectory == true {
383 | continue
384 | }
385 |
386 | // If this file is expired, add it to URLsToDelete
387 | if !onlyForCacheSize,
388 | let expiredDate = expiredDate,
389 | let lastAccessData = resourceValues.contentAccessDate,
390 | (lastAccessData as NSDate).laterDate(expiredDate) == expiredDate
391 | {
392 | urlsToDelete.append(fileUrl)
393 | continue
394 | }
395 |
396 | if let fileSize = resourceValues.totalFileAllocatedSize {
397 | diskCacheSize += UInt(fileSize)
398 | if !onlyForCacheSize {
399 | cachedFiles[fileUrl] = resourceValues
400 | }
401 | }
402 | } catch {
403 | print("DataCache: Error while iterating files \(error.localizedDescription)")
404 | }
405 | }
406 |
407 | return (urlsToDelete, diskCacheSize, cachedFiles)
408 | }
409 |
410 | func cachePath(forKey key: String) -> String {
411 | let fileName = key.md5
412 | return (cachePath as NSString).appendingPathComponent(fileName)
413 | }
414 | }
415 |
416 | #if os(OSX)
417 | extension NSImage {
418 | var pngData: Data? { tiffRepresentation?.bitmap?.png }
419 | }
420 |
421 | extension NSImage {
422 | var jpegData: Data? { tiffRepresentation?.bitmap?.jpeg }
423 | }
424 |
425 | extension NSBitmapImageRep {
426 | var jpeg: Data? { representation(using: .jpeg, properties: [:]) }
427 | }
428 |
429 | extension NSBitmapImageRep {
430 | var png: Data? { representation(using: .png, properties: [:]) }
431 | }
432 | extension Data {
433 | var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
434 | }
435 | #endif
436 |
--------------------------------------------------------------------------------
/Sources/Dictionary+Cache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary+Cache.swift
3 | // Pods
4 | //
5 | // Created by Nguyen Cong Huy on 7/12/16.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension Dictionary {
12 | func keysSortedByValue(_ isOrderedBefore: (Value, Value) -> Bool) -> [Key] {
13 | return Array(self).sorted{ isOrderedBefore($0.1, $1.1) }.map{ $0.0 }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/String+MD5.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+MD5.swift
3 | // Kingfisher
4 | //
5 | // To date, adding CommonCrypto to a Swift framework is problematic. See:
6 | // http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework
7 | // We're using a subset and modified version of CryptoSwift as an alternative.
8 | // The following is an altered source version that only includes MD5. The original software can be found at:
9 | // https://github.com/krzyzanowskim/CryptoSwift
10 | // This is the original copyright notice:
11 |
12 | /*
13 | Copyright (C) 2014 Marcin Krzyżanowski
14 | This software is provided 'as-is', without any express or implied warranty.
15 | In no event will the authors be held liable for any damages arising from the use of this software.
16 | Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
17 | - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
18 | - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
19 | - This notice may not be removed or altered from any source or binary distribution.
20 | */
21 |
22 | import Foundation
23 |
24 | extension String {
25 | var md5: String {
26 | if let data = self.data(using: .utf8, allowLossyConversion: true) {
27 |
28 | let message = data.withUnsafeBytes { (bufferPointer) -> [UInt8] in
29 | return Array(bufferPointer)
30 | }
31 |
32 | let MD5Calculator = MD5(message)
33 | let MD5Data = MD5Calculator.calculate()
34 |
35 | let MD5String = NSMutableString()
36 | for c in MD5Data {
37 | MD5String.appendFormat("%02x", c)
38 | }
39 | return MD5String as String
40 |
41 | } else {
42 | return self
43 | }
44 | }
45 | }
46 |
47 |
48 | /** array of bytes, little-endian representation */
49 | func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] {
50 | let totalBytes = length ?? (MemoryLayout.size * 8)
51 |
52 | let valuePointer = UnsafeMutablePointer.allocate(capacity: 1)
53 | valuePointer.pointee = value
54 |
55 | let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in
56 | var bytes = [UInt8](repeating: 0, count: totalBytes)
57 | for j in 0...size, totalBytes) {
58 | bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
59 | }
60 | return bytes
61 | }
62 |
63 | valuePointer.deinitialize(count: 1)
64 | valuePointer.deallocate()
65 |
66 | return bytes
67 | }
68 |
69 | extension Int {
70 | /** Array of bytes with optional padding (little-endian) */
71 | func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] {
72 | return arrayOfBytes(self, length: totalBytes)
73 | }
74 |
75 | }
76 |
77 | extension NSMutableData {
78 |
79 | /** Convenient way to append bytes */
80 | func appendBytes(_ arrayOfBytes: [UInt8]) {
81 | append(arrayOfBytes, length: arrayOfBytes.count)
82 | }
83 |
84 | }
85 |
86 | protocol HashProtocol {
87 | var message: Array { get }
88 |
89 | /** Common part for hash calculation. Prepare header data. */
90 | func prepare(_ len: Int) -> Array
91 | }
92 |
93 | extension HashProtocol {
94 |
95 | func prepare(_ len: Int) -> Array {
96 | var tmpMessage = message
97 |
98 | // Step 1. Append Padding Bits
99 | tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
100 |
101 | // append "0" bit until message length in bits ≡ 448 (mod 512)
102 | var msgLength = tmpMessage.count
103 | var counter = 0
104 |
105 | while msgLength % len != (len - 8) {
106 | counter += 1
107 | msgLength += 1
108 | }
109 |
110 | tmpMessage += Array(repeating: 0, count: counter)
111 | return tmpMessage
112 | }
113 | }
114 |
115 | func toUInt32Array(_ slice: ArraySlice) -> Array {
116 | var result = Array()
117 | result.reserveCapacity(16)
118 |
119 | for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) {
120 | let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24
121 | let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16
122 | let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8
123 | let d3 = UInt32(slice[idx])
124 | let val: UInt32 = d0 | d1 | d2 | d3
125 |
126 | result.append(val)
127 | }
128 | return result
129 | }
130 |
131 | struct BytesIterator: IteratorProtocol {
132 |
133 | let chunkSize: Int
134 | let data: [UInt8]
135 |
136 | init(chunkSize: Int, data: [UInt8]) {
137 | self.chunkSize = chunkSize
138 | self.data = data
139 | }
140 |
141 | var offset = 0
142 |
143 | mutating func next() -> ArraySlice? {
144 | let end = min(chunkSize, data.count - offset)
145 | let result = data[offset.. 0 ? result : nil
148 | }
149 | }
150 |
151 | struct BytesSequence: Sequence {
152 | let chunkSize: Int
153 | let data: [UInt8]
154 |
155 | func makeIterator() -> BytesIterator {
156 | return BytesIterator(chunkSize: chunkSize, data: data)
157 | }
158 | }
159 |
160 | func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 {
161 | return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits))
162 | }
163 |
164 | class MD5: HashProtocol {
165 |
166 | static let size = 16 // 128 / 8
167 | let message: [UInt8]
168 |
169 | init (_ message: [UInt8]) {
170 | self.message = message
171 | }
172 |
173 | /** specifies the per-round shift amounts */
174 | private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
175 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
176 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
177 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]
178 |
179 | /** binary integer part of the sines of integers (Radians) */
180 | private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
181 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
182 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
183 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
184 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
185 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
186 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
187 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
188 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
189 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
190 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
191 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
192 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
193 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
194 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
195 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]
196 |
197 | private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
198 |
199 | func calculate() -> [UInt8] {
200 | var tmpMessage = prepare(64)
201 | tmpMessage.reserveCapacity(tmpMessage.count + 4)
202 |
203 | // hash values
204 | var hh = hashes
205 |
206 | // Step 2. Append Length a 64-bit representation of lengthInBits
207 | let lengthInBits = (message.count * 8)
208 | let lengthBytes = lengthInBits.bytes(64 / 8)
209 | tmpMessage += lengthBytes.reversed()
210 |
211 | // Process the message in successive 512-bit chunks:
212 | let chunkSizeBytes = 512 / 8 // 64
213 |
214 | for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
215 | // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
216 | let M = toUInt32Array(chunk)
217 | assert(M.count == 16, "Invalid array")
218 |
219 | // Initialize hash value for this chunk:
220 | var A: UInt32 = hh[0]
221 | var B: UInt32 = hh[1]
222 | var C: UInt32 = hh[2]
223 | var D: UInt32 = hh[3]
224 |
225 | var dTemp: UInt32 = 0
226 |
227 | // Main loop
228 | for j in 0 ..< sines.count {
229 | var g = 0
230 | var F: UInt32 = 0
231 |
232 | switch j {
233 | case 0...15:
234 | F = (B & C) | ((~B) & D)
235 | g = j
236 | break
237 | case 16...31:
238 | F = (D & B) | (~D & C)
239 | g = (5 * j + 1) % 16
240 | break
241 | case 32...47:
242 | F = B ^ C ^ D
243 | g = (3 * j + 5) % 16
244 | break
245 | case 48...63:
246 | F = C ^ (B | (~D))
247 | g = (7 * j) % 16
248 | break
249 | default:
250 | break
251 | }
252 | dTemp = D
253 | D = C
254 | C = B
255 | B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j])
256 | A = dTemp
257 | }
258 |
259 | hh[0] = hh[0] &+ A
260 | hh[1] = hh[1] &+ B
261 | hh[2] = hh[2] &+ C
262 | hh[3] = hh[3] &+ D
263 | }
264 |
265 | var result = [UInt8]()
266 | result.reserveCapacity(hh.count / 4)
267 |
268 | hh.forEach {
269 | let itemLE = $0.littleEndian
270 | result += [UInt8(itemLE & 0xff), UInt8((itemLE >> 8) & 0xff), UInt8((itemLE >> 16) & 0xff), UInt8((itemLE >> 24) & 0xff)]
271 | }
272 | return result
273 | }
274 | }
275 |
--------------------------------------------------------------------------------