├── .gitignore
├── .travis.yml
├── Benchmark
├── CacheBenchmark.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── CacheBenchmark
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Benchmark.h
│ ├── Benchmark.m
│ ├── Info.plist
│ ├── ViewController.h
│ ├── ViewController.m
│ └── main.m
├── Result.numbers
├── Result.txt
├── Result_disk.png
├── Result_memory.png
└── Vendor
│ ├── PINCache
│ ├── PINCache.h
│ ├── PINCache.m
│ ├── PINCacheMacros.h
│ ├── PINCacheObjectSubscripting.h
│ ├── PINCaching.h
│ ├── PINDiskCache.h
│ ├── PINDiskCache.m
│ ├── PINMemoryCache.h
│ ├── PINMemoryCache.m
│ └── PINOperation
│ │ ├── PINOperation.h
│ │ ├── PINOperationGroup.h
│ │ ├── PINOperationGroup.m
│ │ ├── PINOperationMacros.h
│ │ ├── PINOperationQueue.h
│ │ ├── PINOperationQueue.m
│ │ └── PINOperationTypes.h
│ ├── SQLite
│ ├── sqlite3.c
│ └── sqlite3.h
│ ├── YYThreadSafeDictionary
│ ├── YYThreadSafeDictionary.h
│ └── YYThreadSafeDictionary.m
│ └── version.txt
├── Framework
├── Info.plist
└── YYCache.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ └── contents.xcworkspacedata
│ └── xcshareddata
│ └── xcschemes
│ └── YYCache.xcscheme
├── LICENSE
├── README.md
├── YYCache.podspec
└── YYCache
├── YYCache.h
├── YYCache.m
├── YYDiskCache.h
├── YYDiskCache.m
├── YYKVStorage.h
├── YYKVStorage.m
├── YYMemoryCache.h
└── YYMemoryCache.m
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | #
6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
7 |
8 | ## Build generated
9 | build/
10 | DerivedData/
11 |
12 | ## Various settings
13 | *.pbxuser
14 | !default.pbxuser
15 | *.mode1v3
16 | !default.mode1v3
17 | *.mode2v3
18 | !default.mode2v3
19 | *.perspectivev3
20 | !default.perspectivev3
21 | xcuserdata/
22 |
23 | ## Other
24 | *.moved-aside
25 | *.xccheckout
26 | *.xcuserstate
27 | *.xcscmblueprint
28 |
29 | ## Obj-C/Swift specific
30 | *.hmap
31 | *.ipa
32 | *.dSYM.zip
33 | *.dSYM
34 |
35 | # CocoaPods
36 | #
37 | # We recommend against adding the Pods directory to your .gitignore. However
38 | # you should judge for yourself, the pros and cons are mentioned at:
39 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
40 | #
41 | # Pods/
42 |
43 | # Carthage
44 | #
45 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
46 | # Carthage/Checkouts
47 |
48 | Carthage/Build
49 |
50 | # fastlane
51 | #
52 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
53 | # screenshots whenever they are needed.
54 | # For more information about the recommended setup visit:
55 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
56 |
57 | fastlane/report.xml
58 | fastlane/Preview.html
59 | fastlane/screenshots
60 | fastlane/test_output
61 |
62 | # Code Injection
63 | #
64 | # After new code Injection tools there's a generated folder /iOSInjectionProject
65 | # https://github.com/johnno1962/injectionforxcode
66 |
67 | iOSInjectionProject/
68 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8
3 | xcode_project: Framework/YYCache.xcodeproj
4 | xcode_scheme: YYCache
5 |
6 | before_install:
7 | - env
8 | - xcodebuild -version
9 | - xcodebuild -showsdks
10 | - xcpretty --version
11 |
12 | script:
13 | - set -o pipefail
14 | - xcodebuild clean build -project "$TRAVIS_XCODE_PROJECT" -scheme "$TRAVIS_XCODE_SCHEME" | xcpretty
15 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // CacheBenchmark
4 | //
5 | // Created by ibireme on 2017/6/29.
6 | // Copyright © 2017年 ibireme. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // CacheBenchmark
4 | //
5 | // Created by ibireme on 2017/6/29.
6 | // Copyright © 2017年 ibireme. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19 | // Override point for customization after application launch.
20 | return YES;
21 | }
22 |
23 |
24 | - (void)applicationWillResignActive:(UIApplication *)application {
25 | // 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.
26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
27 | }
28 |
29 |
30 | - (void)applicationDidEnterBackground:(UIApplication *)application {
31 | // 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.
32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
33 | }
34 |
35 |
36 | - (void)applicationWillEnterForeground:(UIApplication *)application {
37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
38 | }
39 |
40 |
41 | - (void)applicationDidBecomeActive:(UIApplication *)application {
42 | // 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.
43 | }
44 |
45 |
46 | - (void)applicationWillTerminate:(UIApplication *)application {
47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
48 | }
49 |
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/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 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/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 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/Benchmark.h:
--------------------------------------------------------------------------------
1 | //
2 | // Benchmark.h
3 | // CacheBenchmark
4 | //
5 | // Created by ibireme on 15/10/20.
6 | // Copyright (C) 2015 ibireme. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface Benchmark : NSObject
12 | + (void)benchmark;
13 | @end
14 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // CacheBenchmark
4 | //
5 | // Created by ibireme on 2017/6/29.
6 | // Copyright © 2017年 ibireme. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UIViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // CacheBenchmark
4 | //
5 | // Created by ibireme on 2017/6/29.
6 | // Copyright © 2017年 ibireme. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #include "Benchmark.h"
11 |
12 | @interface ViewController ()
13 |
14 | @end
15 |
16 | @implementation ViewController
17 |
18 | - (void)viewDidLoad {
19 | [super viewDidLoad];
20 | // Do any additional setup after loading the view, typically from a nib.
21 |
22 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
23 | [Benchmark benchmark];
24 | });
25 | }
26 |
27 |
28 | - (void)didReceiveMemoryWarning {
29 | [super didReceiveMemoryWarning];
30 | // Dispose of any resources that can be recreated.
31 | }
32 |
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/Benchmark/CacheBenchmark/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // CacheBenchmark
4 | //
5 | // Created by ibireme on 2017/6/29.
6 | // Copyright © 2017年 ibireme. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Benchmark/Result.numbers:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibireme/YYCache/0aac6e84f10b2996ef2ce906db0be1ea6ec24e83/Benchmark/Result.numbers
--------------------------------------------------------------------------------
/Benchmark/Result.txt:
--------------------------------------------------------------------------------
1 | iPhone 6 64G
2 |
3 |
4 | 200000 item in 103 ms
5 | 1000 / 103 * 20000
6 |
7 |
8 | ===========================
9 | Memory cache set 200000 key-value pairs
10 | NSDictionary: 103.59
11 | NSDict+Lock: 126.79
12 | YYMemoryCache: 282.61
13 | PINMemoryCache: 434.40
14 | NSCache: 642.02
15 | TMMemoryCache: 48418.95
16 |
17 | ===========================
18 | Memory cache set 200000 key-value pairs without resize
19 | NSDictionary: 76.55
20 | NSDict+Lock: 96.27
21 | YYMemoryCache: 288.22
22 | PINMemoryCache: 327.28
23 | NSCache: 406.55
24 | TMMemoryCache: 120786.68
25 |
26 | ===========================
27 | Memory cache get 200000 key-value pairs
28 | NSDictionary: 72.75
29 | NSDict+Lock: 79.67
30 | YYMemoryCache: 136.06
31 | PINMemoryCache: 268.18
32 | NSCache: 173.11
33 | TMMemoryCache: 226113.28
34 |
35 | ===========================
36 | Memory cache get 100000 key-value pairs randomly
37 | NSDictionary: 83.09
38 | NSDict+Lock: 91.63
39 | YYMemoryCache: 171.24
40 | PINMemoryCache: 316.13
41 | NSCache: 249.20
42 | TMMemoryCache: 133216.34
43 |
44 | ===========================
45 | Memory cache get 200000 key-value pairs none exist
46 | NSDictionary: 69.78
47 | NSDict+Lock: 77.65
48 | YYMemoryCache: 132.74
49 | PINMemoryCache: 249.25
50 | NSCache: 167.44
51 | TMMemoryCache: 132876.79
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | ///////// sqlite3.dylib (iOS 9.0.2 with SQLite 3.8.10.2) ////////////////////////////////////////////////////////////////////////
68 |
69 |
70 |
71 | write
72 |
73 | ===========================
74 | Disk cache set 1000 key-value pairs (value is NSNumber)
75 | YYKVFile: 8924.52
76 | YYKVSQLite: 492.65
77 | YYDiskCache: 448.79
78 | PINDiskCache: 7707.43
79 | TMDiskCache: 7537.95
80 |
81 | ===========================
82 | Disk cache set 1000 key-value pairs (value is NSData(100KB))
83 | YYKVFile: 9313.82
84 | YYKVSQLite: 6231.08
85 | YYDiskCache: 10539.94
86 | PINDiskCache: 7869.28
87 | TMDiskCache: 7707.65
88 |
89 |
90 |
91 | replace
92 |
93 | ===========================
94 | Disk cache set 1000 key-value pairs (value is NSNumber)
95 | YYKVFile: 8343.03
96 | YYKVSQLite: 821.53
97 | YYDiskCache: 1377.89
98 | PINDiskCache: 7855.98
99 | TMDiskCache: 7845.19
100 |
101 | ===========================
102 | Disk cache set 1000 key-value pairs (value is NSData(100KB))
103 | YYKVFile: 7273.65
104 | YYKVSQLite: 8269.29
105 | YYDiskCache: 6915.04
106 | PINDiskCache: 8017.28
107 | TMDiskCache: 7825.24
108 |
109 |
110 |
111 |
112 |
113 | read
114 |
115 | ===========================
116 | Disk cache get 1000 key-value pairs randomly (value is NSNumber)
117 | YYKVFile: 3457.71
118 | YYKVSQLite: 819.16
119 | YYDiskCache: 685.47
120 | PINDiskCache: 3537.93
121 | TMDiskCache: 2497.36
122 |
123 | ===========================
124 | Disk cache get 1000 key-value pairs randomly (value is NSData(100KB))
125 | YYKVFile: 4082.34
126 | YYKVSQLite: 13413.11
127 | YYDiskCache: 4221.09
128 | PINDiskCache: 4103.34
129 | TMDiskCache: 4425.64
130 |
131 |
132 |
133 | read again (with file-in-memory cache)
134 |
135 | ===========================
136 | Disk cache get 1000 key-value pairs randomly (value is NSNumber)
137 | YYKVFile: 5179.29
138 | YYKVSQLite: 600.79
139 | YYDiskCache: 688.01
140 | PINDiskCache: 2336.50
141 | TMDiskCache: 2513.61
142 |
143 | ===========================
144 | Disk cache get 1000 key-value pairs randomly (value is NSData(100KB))
145 | YYKVFile: 4071.25
146 | YYKVSQLite: 11277.24
147 | YYDiskCache: 5622.77
148 | PINDiskCache: 4279.13
149 | TMDiskCache: 4331.23
150 |
151 |
152 |
153 | read none exist
154 |
155 | ===========================
156 | Disk cache get 1000 key-value pairs none exist
157 | YYKVFile: 16.63
158 | YYKVSQLite: 17.53
159 | YYDiskCache: 17.61
160 | PINDiskCache: 64.95
161 | TMDiskCache: 194.90
162 |
163 | ===========================
164 | Disk cache get 1000 key-value pairs none exist
165 | YYKVFile: 14.45
166 | YYKVSQLite: 18.81
167 | YYDiskCache: 17.58
168 | PINDiskCache: 66.44
169 | TMDiskCache: 196.30
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | ///////// SQLite download from www.sqlite.org (iOS 9.0.2 with SQLite 3.9.1) ////////////////////////////////////////////////////////////////////////
194 |
195 |
196 |
197 | write
198 |
199 | ===========================
200 | Disk cache set 1000 key-value pairs (value is NSNumber)
201 | YYKVFile: 8561.47
202 | YYKVSQLite: 159.68
203 | YYDiskCache: 174.67
204 | PINDiskCache: 7662.53
205 | TMDiskCache: 7578.53
206 |
207 | ===========================
208 | Disk cache set 1000 key-value pairs (value is NSData(100KB))
209 | YYKVFile: 9103.42
210 | YYKVSQLite: 8427.90
211 | YYDiskCache: 9168.39
212 | PINDiskCache: 7884.27
213 | TMDiskCache: 7692.10
214 |
215 |
216 |
217 | replace
218 |
219 | ===========================
220 | Disk cache set 1000 key-value pairs (value is NSNumber)
221 | YYKVFile: 5309.11
222 | YYKVSQLite: 252.62
223 | YYDiskCache: 501.28
224 | PINDiskCache: 8489.18
225 | TMDiskCache: 7741.30
226 |
227 | ===========================
228 | Disk cache set 1000 key-value pairs (value is NSData(100KB))
229 | YYKVFile: 6216.99
230 | YYKVSQLite: 5356.37
231 | YYDiskCache: 6592.80
232 | PINDiskCache: 8039.93
233 | TMDiskCache: 7834.50
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 | read
242 |
243 | ===========================
244 | Disk cache get 1000 key-value pairs randomly (value is NSNumber)
245 | YYKVFile: 2244.99
246 | YYKVSQLite: 258.65
247 | YYDiskCache: 225.87
248 | PINDiskCache: 2427.45
249 | TMDiskCache: 2828.23
250 |
251 | ===========================
252 | Disk cache get 1000 key-value pairs randomly (value is NSData(100KB))
253 | YYKVFile: 2983.38
254 | YYKVSQLite: 14725.93
255 | YYDiskCache: 3260.34
256 | PINDiskCache: 4300.85
257 | TMDiskCache: 4437.85
258 |
259 |
260 |
261 | read again (with file-in-memory cache)
262 |
263 | ===========================
264 | Disk cache get 1000 key-value pairs randomly (value is NSNumber)
265 | YYKVFile: 3560.50
266 | YYKVSQLite: 739.11
267 | YYDiskCache: 324.97
268 | PINDiskCache: 2396.57
269 | TMDiskCache: 2554.92
270 |
271 | ===========================
272 | Disk cache get 1000 key-value pairs randomly (value is NSData(100KB))
273 | YYKVFile: 3051.79
274 | YYKVSQLite: 13604.69
275 | YYDiskCache: 2857.67
276 | PINDiskCache: 4365.52
277 | TMDiskCache: 4373.01
278 |
279 |
280 | read none exist
281 |
282 | ===========================
283 | Disk cache get 1000 key-value pairs none exist
284 | YYKVFile: 16.71
285 | YYKVSQLite: 16.21
286 | YYDiskCache: 19.27
287 | PINDiskCache: 66.73
288 | TMDiskCache: 199.49
289 |
290 | ===========================
291 | Disk cache get 1000 key-value pairs none exist
292 | YYKVFile: 15.44
293 | YYKVSQLite: 18.64
294 | YYDiskCache: 16.90
295 | PINDiskCache: 70.25
296 | TMDiskCache: 198.95
297 |
298 |
--------------------------------------------------------------------------------
/Benchmark/Result_disk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibireme/YYCache/0aac6e84f10b2996ef2ce906db0be1ea6ec24e83/Benchmark/Result_disk.png
--------------------------------------------------------------------------------
/Benchmark/Result_memory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ibireme/YYCache/0aac6e84f10b2996ef2ce906db0be1ea6ec24e83/Benchmark/Result_memory.png
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINCache.h:
--------------------------------------------------------------------------------
1 | // PINCache is a modified version of TMCache
2 | // Modifications by Garrett Moon
3 | // Copyright (c) 2015 Pinterest. All rights reserved.
4 |
5 | #import
6 |
7 | #import
8 | #import
9 | #import
10 | #import
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @class PINCache;
15 |
16 |
17 | /**
18 | `PINCache` is a thread safe key/value store designed for persisting temporary objects that are expensive to
19 | reproduce, such as downloaded data or the results of slow processing. It is comprised of two self-similar
20 | stores, one in memory () and one on disk ().
21 |
22 | `PINCache` itself actually does very little; its main function is providing a front end for a common use case:
23 | a small, fast memory cache that asynchronously persists itself to a large, slow disk cache. When objects are
24 | removed from the memory cache in response to an "apocalyptic" event they remain in the disk cache and are
25 | repopulated in memory the next time they are accessed. `PINCache` also does the tedious work of creating a
26 | dispatch group to wait for both caches to finish their operations without blocking each other.
27 |
28 | The parallel caches are accessible as public properties ( and ) and can be manipulated
29 | separately if necessary. See the docs for and for more details.
30 |
31 | @warning when using in extension or watch extension, define PIN_APP_EXTENSIONS=1
32 | */
33 |
34 | PIN_SUBCLASSING_RESTRICTED
35 | @interface PINCache : NSObject
36 |
37 | #pragma mark -
38 | /// @name Core
39 |
40 | /**
41 | Synchronously retrieves the total byte count of the on the shared disk queue.
42 | */
43 | @property (readonly) NSUInteger diskByteCount;
44 |
45 | /**
46 | The underlying disk cache, see for additional configuration and trimming options.
47 | */
48 | @property (readonly) PINDiskCache *diskCache;
49 |
50 | /**
51 | The underlying memory cache, see for additional configuration and trimming options.
52 | */
53 | @property (readonly) PINMemoryCache *memoryCache;
54 |
55 | #pragma mark - Lifecycle
56 | /// @name Initialization
57 |
58 | /**
59 | A shared cache.
60 |
61 | @result The shared singleton cache instance.
62 | */
63 | @property (class, strong, readonly) PINCache *sharedCache;
64 |
65 | - (instancetype)init NS_UNAVAILABLE;
66 |
67 | /**
68 | Multiple instances with the same name are *not* allowed and can *not* safely
69 | access the same data on disk. Also used to create the .
70 |
71 | @see name
72 | @param name The name of the cache.
73 | @result A new cache with the specified name.
74 | */
75 | - (instancetype)initWithName:(nonnull NSString *)name;
76 |
77 | /**
78 | Multiple instances with the same name are *not* allowed and can *not* safely
79 | access the same data on disk. Also used to create the .
80 |
81 | @see name
82 | @param name The name of the cache.
83 | @param rootPath The path of the cache on disk.
84 | @result A new cache with the specified name.
85 | */
86 | - (instancetype)initWithName:(nonnull NSString *)name rootPath:(nonnull NSString *)rootPath;
87 |
88 | /**
89 | Multiple instances with the same name are *not* allowed and can *not* safely
90 | access the same data on disk.. Also used to create the .
91 | Initializer allows you to override default NSKeyedArchiver/NSKeyedUnarchiver serialization for .
92 | You must provide both serializer and deserializer, or opt-out to default implementation providing nil values.
93 |
94 | @see name
95 | @param name The name of the cache.
96 | @param rootPath The path of the cache on disk.
97 | @param serializer A block used to serialize object before writing to disk. If nil provided, default NSKeyedArchiver serialized will be used.
98 | @param deserializer A block used to deserialize object read from disk. If nil provided, default NSKeyedUnarchiver serialized will be used.
99 | @result A new cache with the specified name.
100 | */
101 | - (instancetype)initWithName:(NSString *)name
102 | rootPath:(NSString *)rootPath
103 | serializer:(nullable PINDiskCacheSerializerBlock)serializer
104 | deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer;
105 |
106 |
107 | /**
108 | Multiple instances with the same name are *not* allowed and can *not* safely
109 | access the same data on disk. Also used to create the .
110 | Initializer allows you to override default NSKeyedArchiver/NSKeyedUnarchiver serialization for .
111 | You must provide both serializer and deserializer, or opt-out to default implementation providing nil values.
112 |
113 | @see name
114 | @param name The name of the cache.
115 | @param rootPath The path of the cache on disk.
116 | @param serializer A block used to serialize object before writing to disk. If nil provided, default NSKeyedArchiver serialized will be used.
117 | @param deserializer A block used to deserialize object read from disk. If nil provided, default NSKeyedUnarchiver serialized will be used.
118 | @param keyEncoder A block used to encode key(filename). If nil provided, default url encoder will be used
119 | @param keyDecoder A block used to decode key(filename). If nil provided, default url decoder will be used
120 | @result A new cache with the specified name.
121 | */
122 | - (instancetype)initWithName:(nonnull NSString *)name
123 | rootPath:(nonnull NSString *)rootPath
124 | serializer:(nullable PINDiskCacheSerializerBlock)serializer
125 | deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer
126 | keyEncoder:(nullable PINDiskCacheKeyEncoderBlock)keyEncoder
127 | keyDecoder:(nullable PINDiskCacheKeyDecoderBlock)keyDecoder NS_DESIGNATED_INITIALIZER;
128 |
129 | @end
130 |
131 | @interface PINCache (Deprecated)
132 | - (void)containsObjectForKey:(NSString *)key block:(PINCacheObjectContainmentBlock)block __attribute__((deprecated));
133 | - (void)objectForKey:(NSString *)key block:(PINCacheObjectBlock)block __attribute__((deprecated));
134 | - (void)setObject:(id )object forKey:(NSString *)key block:(nullable PINCacheObjectBlock)block __attribute__((deprecated));
135 | - (void)setObject:(id )object forKey:(NSString *)key withCost:(NSUInteger)cost block:(nullable PINCacheObjectBlock)block __attribute__((deprecated));
136 | - (void)removeObjectForKey:(NSString *)key block:(nullable PINCacheObjectBlock)block __attribute__((deprecated));
137 | - (void)trimToDate:(NSDate *)date block:(nullable PINCacheBlock)block __attribute__((deprecated));
138 | - (void)removeAllObjects:(nullable PINCacheBlock)block __attribute__((deprecated));
139 | @end
140 |
141 | NS_ASSUME_NONNULL_END
142 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINCache.m:
--------------------------------------------------------------------------------
1 | // PINCache is a modified version of PINCache
2 | // Modifications by Garrett Moon
3 | // Copyright (c) 2015 Pinterest. All rights reserved.
4 |
5 | #import "PINCache.h"
6 |
7 | #import
8 |
9 | static NSString * const PINCachePrefix = @"com.pinterest.PINCache";
10 | static NSString * const PINCacheSharedName = @"PINCacheShared";
11 |
12 | @interface PINCache ()
13 | @property (copy, nonatomic) NSString *name;
14 | @property (strong, nonatomic) PINOperationQueue *operationQueue;
15 | @end
16 |
17 | @implementation PINCache
18 |
19 | #pragma mark - Initialization -
20 |
21 | - (instancetype)init
22 | {
23 | @throw [NSException exceptionWithName:@"Must initialize with a name" reason:@"PINCache must be initialized with a name. Call initWithName: instead." userInfo:nil];
24 | return [self initWithName:@""];
25 | }
26 |
27 | - (instancetype)initWithName:(NSString *)name
28 | {
29 | return [self initWithName:name rootPath:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]];
30 | }
31 |
32 | - (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath
33 | {
34 | return [self initWithName:name rootPath:rootPath serializer:nil deserializer:nil];
35 | }
36 |
37 | - (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath serializer:(PINDiskCacheSerializerBlock)serializer deserializer:(PINDiskCacheDeserializerBlock)deserializer {
38 | return [self initWithName:name rootPath:rootPath serializer:serializer deserializer:deserializer keyEncoder:nil keyDecoder:nil];
39 | }
40 |
41 | - (instancetype)initWithName:(NSString *)name
42 | rootPath:(NSString *)rootPath
43 | serializer:(PINDiskCacheSerializerBlock)serializer
44 | deserializer:(PINDiskCacheDeserializerBlock)deserializer
45 | keyEncoder:(PINDiskCacheKeyEncoderBlock)keyEncoder
46 | keyDecoder:(PINDiskCacheKeyDecoderBlock)keyDecoder
47 | {
48 | if (!name)
49 | return nil;
50 |
51 | if (self = [super init]) {
52 | _name = [name copy];
53 |
54 | //10 may actually be a bit high, but currently much of our threads are blocked on empyting the trash. Until we can resolve that, lets bump this up.
55 | _operationQueue = [[PINOperationQueue alloc] initWithMaxConcurrentOperations:10];
56 | _diskCache = [[PINDiskCache alloc] initWithName:_name
57 | prefix:PINDiskCachePrefix
58 | rootPath:rootPath
59 | serializer:serializer
60 | deserializer:deserializer
61 | keyEncoder:nil
62 | keyDecoder:nil
63 | operationQueue:_operationQueue];
64 | _memoryCache = [[PINMemoryCache alloc] initWithOperationQueue:_operationQueue];
65 | }
66 | return self;
67 | }
68 |
69 | - (NSString *)description
70 | {
71 | return [[NSString alloc] initWithFormat:@"%@.%@.%p", PINCachePrefix, _name, (void *)self];
72 | }
73 |
74 | + (PINCache *)sharedCache
75 | {
76 | static PINCache *cache;
77 | static dispatch_once_t predicate;
78 |
79 | dispatch_once(&predicate, ^{
80 | cache = [[PINCache alloc] initWithName:PINCacheSharedName];
81 | });
82 |
83 | return cache;
84 | }
85 |
86 | #pragma mark - Public Asynchronous Methods -
87 |
88 | - (void)containsObjectForKeyAsync:(NSString *)key completion:(PINCacheObjectContainmentBlock)block
89 | {
90 | if (!key || !block) {
91 | return;
92 | }
93 |
94 | __weak PINCache *weakSelf = self;
95 |
96 | [self.operationQueue addOperation:^{
97 | PINCache *strongSelf = weakSelf;
98 |
99 | BOOL containsObject = [strongSelf containsObjectForKey:key];
100 | block(containsObject);
101 | }];
102 | }
103 |
104 | #pragma clang diagnostic push
105 | #pragma clang diagnostic ignored "-Wshadow"
106 |
107 | - (void)objectForKeyAsync:(NSString *)key completion:(PINCacheObjectBlock)block
108 | {
109 | if (!key || !block)
110 | return;
111 |
112 | __weak PINCache *weakSelf = self;
113 |
114 | [self.operationQueue addOperation:^{
115 | PINCache *strongSelf = weakSelf;
116 | if (!strongSelf)
117 | return;
118 | [strongSelf->_memoryCache objectForKeyAsync:key completion:^(PINMemoryCache *memoryCache, NSString *memoryCacheKey, id memoryCacheObject) {
119 | PINCache *strongSelf = weakSelf;
120 | if (!strongSelf)
121 | return;
122 |
123 | if (memoryCacheObject) {
124 | // Update file modification date. TODO: make this a separate method?
125 | [strongSelf->_diskCache fileURLForKeyAsync:memoryCacheKey completion:^(NSString * _Nonnull key, NSURL * _Nullable fileURL) {}];
126 | [strongSelf->_operationQueue addOperation:^{
127 | PINCache *strongSelf = weakSelf;
128 | if (strongSelf)
129 | block(strongSelf, memoryCacheKey, memoryCacheObject);
130 | }];
131 | } else {
132 | [strongSelf->_diskCache objectForKeyAsync:memoryCacheKey completion:^(PINDiskCache *diskCache, NSString *diskCacheKey, id diskCacheObject) {
133 | PINCache *strongSelf = weakSelf;
134 | if (!strongSelf)
135 | return;
136 |
137 | [strongSelf->_memoryCache setObjectAsync:diskCacheObject forKey:diskCacheKey completion:nil];
138 |
139 | [strongSelf->_operationQueue addOperation:^{
140 | PINCache *strongSelf = weakSelf;
141 | if (strongSelf)
142 | block(strongSelf, diskCacheKey, diskCacheObject);
143 | }];
144 | }];
145 | }
146 | }];
147 | }];
148 | }
149 |
150 | #pragma clang diagnostic pop
151 |
152 | - (void)setObjectAsync:(id )object forKey:(NSString *)key completion:(PINCacheObjectBlock)block
153 | {
154 | [self setObjectAsync:object forKey:key withCost:0 completion:block];
155 | }
156 |
157 | - (void)setObjectAsync:(id )object forKey:(NSString *)key withCost:(NSUInteger)cost completion:(PINCacheObjectBlock)block
158 | {
159 | if (!key || !object)
160 | return;
161 |
162 | PINOperationGroup *group = [PINOperationGroup asyncOperationGroupWithQueue:_operationQueue];
163 |
164 | [group addOperation:^{
165 | [_memoryCache setObject:object forKey:key withCost:cost];
166 | }];
167 | [group addOperation:^{
168 | [_diskCache setObject:object forKey:key];
169 | }];
170 |
171 | if (block) {
172 | [group setCompletion:^{
173 | block(self, key, object);
174 | }];
175 | }
176 |
177 | [group start];
178 | }
179 |
180 | - (void)removeObjectForKeyAsync:(NSString *)key completion:(PINCacheObjectBlock)block
181 | {
182 | if (!key)
183 | return;
184 |
185 | PINOperationGroup *group = [PINOperationGroup asyncOperationGroupWithQueue:_operationQueue];
186 |
187 | [group addOperation:^{
188 | [_memoryCache removeObjectForKey:key];
189 | }];
190 | [group addOperation:^{
191 | [_diskCache removeObjectForKey:key];
192 | }];
193 |
194 | if (block) {
195 | [group setCompletion:^{
196 | block(self, key, nil);
197 | }];
198 | }
199 |
200 | [group start];
201 | }
202 |
203 | - (void)removeAllObjectsAsync:(PINCacheBlock)block
204 | {
205 | PINOperationGroup *group = [PINOperationGroup asyncOperationGroupWithQueue:_operationQueue];
206 |
207 | [group addOperation:^{
208 | [_memoryCache removeAllObjects];
209 | }];
210 | [group addOperation:^{
211 | [_diskCache removeAllObjects];
212 | }];
213 |
214 | if (block) {
215 | [group setCompletion:^{
216 | block(self);
217 | }];
218 | }
219 |
220 | [group start];
221 | }
222 |
223 | - (void)trimToDateAsync:(NSDate *)date completion:(PINCacheBlock)block
224 | {
225 | if (!date)
226 | return;
227 |
228 | PINOperationGroup *group = [PINOperationGroup asyncOperationGroupWithQueue:_operationQueue];
229 |
230 | [group addOperation:^{
231 | [_memoryCache trimToDate:date];
232 | }];
233 | [group addOperation:^{
234 | [_diskCache trimToDate:date];
235 | }];
236 |
237 | if (block) {
238 | [group setCompletion:^{
239 | block(self);
240 | }];
241 | }
242 |
243 | [group start];
244 | }
245 |
246 | #pragma mark - Public Synchronous Accessors -
247 |
248 | - (NSUInteger)diskByteCount
249 | {
250 | __block NSUInteger byteCount = 0;
251 |
252 | [_diskCache synchronouslyLockFileAccessWhileExecutingBlock:^(PINDiskCache *diskCache) {
253 | byteCount = diskCache.byteCount;
254 | }];
255 |
256 | return byteCount;
257 | }
258 |
259 | - (BOOL)containsObjectForKey:(NSString *)key
260 | {
261 | if (!key)
262 | return NO;
263 |
264 | return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
265 | }
266 |
267 | - (nullable id)objectForKey:(NSString *)key
268 | {
269 | if (!key)
270 | return nil;
271 |
272 | __block id object = nil;
273 |
274 | object = [_memoryCache objectForKey:key];
275 |
276 | if (object) {
277 | // Update file modification date. TODO: make this a separate method?
278 | [_diskCache fileURLForKeyAsync:key completion:^(NSString * _Nonnull key, NSURL * _Nullable fileURL) {}];
279 | } else {
280 | object = [_diskCache objectForKey:key];
281 | [_memoryCache setObject:object forKey:key];
282 | }
283 |
284 | return object;
285 | }
286 |
287 | - (void)setObject:(id )object forKey:(NSString *)key
288 | {
289 | [self setObject:object forKey:key withCost:0];
290 | }
291 |
292 | - (void)setObject:(id )object forKey:(NSString *)key withCost:(NSUInteger)cost
293 | {
294 | if (!key || !object)
295 | return;
296 |
297 | [_memoryCache setObject:object forKey:key withCost:cost];
298 | [_diskCache setObject:object forKey:key];
299 | }
300 |
301 | - (nullable id)objectForKeyedSubscript:(NSString *)key
302 | {
303 | return [self objectForKey:key];
304 | }
305 |
306 | - (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key
307 | {
308 | if (obj == nil) {
309 | [self removeObjectForKey:key];
310 | } else {
311 | [self setObject:obj forKey:key];
312 | }
313 | }
314 |
315 | - (void)removeObjectForKey:(NSString *)key
316 | {
317 | if (!key)
318 | return;
319 |
320 | [_memoryCache removeObjectForKey:key];
321 | [_diskCache removeObjectForKey:key];
322 | }
323 |
324 | - (void)trimToDate:(NSDate *)date
325 | {
326 | if (!date)
327 | return;
328 |
329 | [_memoryCache trimToDate:date];
330 | [_diskCache trimToDate:date];
331 | }
332 |
333 | - (void)removeAllObjects
334 | {
335 | [_memoryCache removeAllObjects];
336 | [_diskCache removeAllObjects];
337 | }
338 |
339 | @end
340 |
341 | @implementation PINCache (Deprecated)
342 |
343 | - (void)containsObjectForKey:(NSString *)key block:(PINCacheObjectContainmentBlock)block
344 | {
345 | [self containsObjectForKeyAsync:key completion:block];
346 | }
347 |
348 | - (void)objectForKey:(NSString *)key block:(PINCacheObjectBlock)block
349 | {
350 | [self objectForKeyAsync:key completion:block];
351 | }
352 |
353 | - (void)setObject:(id )object forKey:(NSString *)key block:(nullable PINCacheObjectBlock)block
354 | {
355 | [self setObjectAsync:object forKey:key completion:block];
356 | }
357 |
358 | - (void)setObject:(id )object forKey:(NSString *)key withCost:(NSUInteger)cost block:(nullable PINCacheObjectBlock)block
359 | {
360 | [self setObjectAsync:object forKey:key withCost:cost completion:block];
361 | }
362 |
363 | - (void)removeObjectForKey:(NSString *)key block:(nullable PINCacheObjectBlock)block
364 | {
365 | [self removeObjectForKeyAsync:key completion:block];
366 | }
367 |
368 | - (void)trimToDate:(NSDate *)date block:(nullable PINCacheBlock)block
369 | {
370 | [self trimToDateAsync:date completion:block];
371 | }
372 |
373 | - (void)removeAllObjects:(nullable PINCacheBlock)block
374 | {
375 | [self removeAllObjectsAsync:block];
376 | }
377 |
378 | @end
379 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINCacheMacros.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINCacheMacros.h
3 | // PINCache
4 | //
5 | // Created by Adlai Holler on 1/31/17.
6 | // Copyright © 2017 Pinterest. All rights reserved.
7 | //
8 |
9 | #ifndef PIN_SUBCLASSING_RESTRICTED
10 | #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
11 | #define PIN_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted))
12 | #else
13 | #define PIN_SUBCLASSING_RESTRICTED
14 | #endif // #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
15 | #endif // #ifndef PIN_SUBCLASSING_RESTRICTED
16 |
17 | #ifndef PIN_NOESCAPE
18 | #if defined(__has_attribute) && __has_attribute(noescape)
19 | #define PIN_NOESCAPE __attribute__((noescape))
20 | #else
21 | #define PIN_NOESCAPE
22 | #endif // #if defined(__has_attribute) && __has_attribute(noescape)
23 | #endif // #ifndef PIN_NOESCAPE
24 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINCacheObjectSubscripting.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINCacheObjectSubscripting.h
3 | // PINCache
4 | //
5 | // Created by Rocir Marcos Leite Santiago on 4/2/16.
6 | // Copyright © 2016 Pinterest. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @protocol PINCacheObjectSubscripting
14 |
15 | @required
16 |
17 | /**
18 | This method enables using literals on the receiving object, such as `id object = cache[@"key"];`.
19 |
20 | @param key The key associated with the object.
21 | @result The object for the specified key.
22 | */
23 | - (nullable id)objectForKeyedSubscript:(NSString *)key;
24 |
25 | /**
26 | This method enables using literals on the receiving object, such as `cache[@"key"] = object;`.
27 |
28 | @param object An object to be assigned for the key. Pass `nil` to remove the existing object for this key.
29 | @param key A key to associate with the object. This string will be copied.
30 | */
31 | - (void)setObject:(nullable id)object forKeyedSubscript:(NSString *)key;
32 |
33 | @end
34 |
35 | NS_ASSUME_NONNULL_END
36 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINCaching.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINCaching.h
3 | // PINCache
4 | //
5 | // Created by Michael Schneider on 1/31/17.
6 | // Copyright © 2017 Pinterest. All rights reserved.
7 | //
8 |
9 | #pragma once
10 | #import
11 |
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | @protocol PINCaching;
16 |
17 | /**
18 | A callback block which provides only the cache as an argument
19 | */
20 | typedef void (^PINCacheBlock)(id cache);
21 |
22 | /**
23 | A callback block which provides the cache, key and object as arguments
24 | */
25 | typedef void (^PINCacheObjectBlock)(id cache, NSString *key, id _Nullable object);
26 |
27 | /**
28 | A callback block which provides a BOOL value as argument
29 | */
30 | typedef void (^PINCacheObjectContainmentBlock)(BOOL containsObject);
31 |
32 | @protocol PINCaching
33 |
34 | #pragma mark - Core
35 |
36 | /**
37 | The name of this cache, used to create a directory under Library/Caches and also appearing in stack traces.
38 | */
39 | @property (readonly) NSString *name;
40 |
41 | #pragma mark - Asynchronous Methods
42 |
43 | /// @name Asynchronous Methods
44 |
45 | /**
46 | This method determines whether an object is present for the given key in the cache. This method returns immediately
47 | and executes the passed block after the object is available, potentially in parallel with other blocks on the
48 | .
49 |
50 | @see containsObjectForKey:
51 | @param key The key associated with the object.
52 | @param block A block to be executed concurrently after the containment check happened
53 | */
54 | - (void)containsObjectForKeyAsync:(NSString *)key completion:(PINCacheObjectContainmentBlock)block;
55 |
56 | /**
57 | Retrieves the object for the specified key. This method returns immediately and executes the passed
58 | block after the object is available, potentially in parallel with other blocks on the .
59 |
60 | @param key The key associated with the requested object.
61 | @param block A block to be executed concurrently when the object is available.
62 | */
63 | - (void)objectForKeyAsync:(NSString *)key completion:(PINCacheObjectBlock)block;
64 |
65 | /**
66 | Stores an object in the cache for the specified key. This method returns immediately and executes the
67 | passed block after the object has been stored, potentially in parallel with other blocks on the .
68 |
69 | @param object An object to store in the cache.
70 | @param key A key to associate with the object. This string will be copied.
71 | @param block A block to be executed concurrently after the object has been stored, or nil.
72 | */
73 | - (void)setObjectAsync:(id)object forKey:(NSString *)key completion:(nullable PINCacheObjectBlock)block;
74 |
75 | /**
76 | Stores an object in the cache for the specified key and the specified memory cost. If the cost causes the total
77 | to go over the the cache is trimmed (oldest objects first). This method returns immediately
78 | and executes the passed block after the object has been stored, potentially in parallel with other blocks
79 | on the .
80 |
81 | @param object An object to store in the cache.
82 | @param key A key to associate with the object. This string will be copied.
83 | @param cost An amount to add to the .
84 | @param block A block to be executed concurrently after the object has been stored, or nil.
85 | */
86 | - (void)setObjectAsync:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost completion:(nullable PINCacheObjectBlock)block;
87 |
88 | /**
89 | Removes the object for the specified key. This method returns immediately and executes the passed
90 | block after the object has been removed, potentially in parallel with other blocks on the .
91 |
92 | @param key The key associated with the object to be removed.
93 | @param block A block to be executed concurrently after the object has been removed, or nil.
94 | */
95 | - (void)removeObjectForKeyAsync:(NSString *)key completion:(nullable PINCacheObjectBlock)block;
96 |
97 | /**
98 | Removes all objects from the cache that have not been used since the specified date. This method returns immediately and
99 | executes the passed block after the cache has been trimmed, potentially in parallel with other blocks on the .
100 |
101 | @param date Objects that haven't been accessed since this date are removed from the cache.
102 | @param block A block to be executed concurrently after the cache has been trimmed, or nil.
103 | */
104 | - (void)trimToDateAsync:(NSDate *)date completion:(nullable PINCacheBlock)block;
105 |
106 | /**
107 | Removes all objects from the cache.This method returns immediately and executes the passed block after the
108 | cache has been cleared, potentially in parallel with other blocks on the .
109 |
110 | @param block A block to be executed concurrently after the cache has been cleared, or nil.
111 | */
112 | - (void)removeAllObjectsAsync:(nullable PINCacheBlock)block;
113 |
114 |
115 | #pragma mark - Synchronous Methods
116 | /// @name Synchronous Methods
117 |
118 | /**
119 | This method determines whether an object is present for the given key in the cache.
120 |
121 | @see containsObjectForKeyAsync:completion:
122 | @param key The key associated with the object.
123 | @result YES if an object is present for the given key in the cache, otherwise NO.
124 | */
125 | - (BOOL)containsObjectForKey:(NSString *)key;
126 |
127 | /**
128 | Retrieves the object for the specified key. This method blocks the calling thread until the object is available.
129 | Uses a lock to achieve synchronicity on the disk cache.
130 |
131 | @see objectForKeyAsync:completion:
132 | @param key The key associated with the object.
133 | @result The object for the specified key.
134 | */
135 | - (nullable id)objectForKey:(NSString *)key;
136 |
137 | /**
138 | Stores an object in the cache for the specified key. This method blocks the calling thread until the object has been set.
139 | Uses a lock to achieve synchronicity on the disk cache.
140 |
141 | @see setObjectAsync:forKey:completion:
142 | @param object An object to store in the cache.
143 | @param key A key to associate with the object. This string will be copied.
144 | */
145 | - (void)setObject:(nullable id)object forKey:(NSString *)key;
146 |
147 | /**
148 | Stores an object in the cache for the specified key and the specified memory cost. If the cost causes the total
149 | to go over the the cache is trimmed (oldest objects first). This method blocks the calling thread
150 | until the object has been stored.
151 |
152 | @param object An object to store in the cache.
153 | @param key A key to associate with the object. This string will be copied.
154 | @param cost An amount to add to the .
155 | */
156 | - (void)setObject:(nullable id)object forKey:(NSString *)key withCost:(NSUInteger)cost;
157 |
158 | /**
159 | Removes the object for the specified key. This method blocks the calling thread until the object
160 | has been removed.
161 | Uses a lock to achieve synchronicity on the disk cache.
162 |
163 | @see removeObjectForKeyAsync:completion:
164 | @param key The key associated with the object to be removed.
165 | */
166 | - (void)removeObjectForKey:(NSString *)key;
167 |
168 | /**
169 | Removes all objects from the cache that have not been used since the specified date.
170 | This method blocks the calling thread until the cache has been trimmed.
171 | Uses a lock to achieve synchronicity on the disk cache.
172 |
173 | @see trimToDateAsync:completion:
174 | @param date Objects that haven't been accessed since this date are removed from the cache.
175 | */
176 | - (void)trimToDate:(NSDate *)date;
177 |
178 | /**
179 | Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
180 | Uses a lock to achieve synchronicity on the disk cache.
181 |
182 | @see removeAllObjectsAsync:
183 | */
184 | - (void)removeAllObjects;
185 |
186 | @end
187 |
188 | NS_ASSUME_NONNULL_END
189 |
190 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINMemoryCache.h:
--------------------------------------------------------------------------------
1 | // PINCache is a modified version of TMCache
2 | // Modifications by Garrett Moon
3 | // Copyright (c) 2015 Pinterest. All rights reserved.
4 |
5 | #import
6 |
7 | #import
8 | #import
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class PINMemoryCache;
14 | @class PINOperationQueue;
15 |
16 |
17 | /**
18 | `PINMemoryCache` is a fast, thread safe key/value store similar to `NSCache`. On iOS it will clear itself
19 | automatically to reduce memory usage when the app receives a memory warning or goes into the background.
20 |
21 | Access is natively synchronous. Asynchronous variations are provided. Every asynchronous method accepts a
22 | callback block that runs on a concurrent , with cache reads and writes protected by a lock.
23 |
24 | All access to the cache is dated so the that the least-used objects can be trimmed first. Setting an
25 | optional will trigger a GCD timer to periodically to trim the cache to that age.
26 |
27 | Objects can optionally be set with a "cost", which could be a byte count or any other meaningful integer.
28 | Setting a will automatically keep the cache below that value with .
29 |
30 | Values will not persist after application relaunch or returning from the background. See for
31 | a memory cache backed by a disk cache.
32 | */
33 |
34 | PIN_SUBCLASSING_RESTRICTED
35 | @interface PINMemoryCache : NSObject
36 |
37 | #pragma mark - Properties
38 | /// @name Core
39 |
40 | /**
41 | The total accumulated cost.
42 | */
43 | @property (readonly) NSUInteger totalCost;
44 |
45 | /**
46 | The maximum cost allowed to accumulate before objects begin to be removed with .
47 | */
48 | @property (assign) NSUInteger costLimit;
49 |
50 | /**
51 | The maximum number of seconds an object is allowed to exist in the cache. Setting this to a value
52 | greater than `0.0` will start a recurring GCD timer with the same period that calls .
53 | Setting it back to `0.0` will stop the timer. Defaults to `0.0`.
54 | */
55 | @property (assign) NSTimeInterval ageLimit;
56 |
57 | /**
58 | If ttlCache is YES, the cache behaves like a ttlCache. This means that once an object enters the
59 | cache, it only lives as long as self.ageLimit. This has the following implications:
60 | - Accessing an object in the cache does not extend that object's lifetime in the cache
61 | - When attempting to access an object in the cache that has lived longer than self.ageLimit,
62 | the cache will behave as if the object does not exist
63 |
64 | */
65 | @property (nonatomic, assign, getter=isTTLCache) BOOL ttlCache;
66 |
67 | /**
68 | When `YES` on iOS the cache will remove all objects when the app receives a memory warning.
69 | Defaults to `YES`.
70 | */
71 | @property (assign) BOOL removeAllObjectsOnMemoryWarning;
72 |
73 | /**
74 | When `YES` on iOS the cache will remove all objects when the app enters the background.
75 | Defaults to `YES`.
76 | */
77 | @property (assign) BOOL removeAllObjectsOnEnteringBackground;
78 |
79 | #pragma mark - Event Blocks
80 | /// @name Event Blocks
81 |
82 | /**
83 | A block to be executed just before an object is added to the cache. This block will be excuted within
84 | a lock, i.e. all reads and writes are suspended for the duration of the block.
85 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
86 | */
87 | @property (nullable, copy) PINCacheObjectBlock willAddObjectBlock;
88 |
89 | /**
90 | A block to be executed just before an object is removed from the cache. This block will be excuted
91 | within a lock, i.e. all reads and writes are suspended for the duration of the block.
92 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
93 | */
94 | @property (nullable, copy) PINCacheObjectBlock willRemoveObjectBlock;
95 |
96 | /**
97 | A block to be executed just before all objects are removed from the cache as a result of .
98 | This block will be excuted within a lock, i.e. all reads and writes are suspended for the duration of the block.
99 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
100 | */
101 | @property (nullable, copy) PINCacheBlock willRemoveAllObjectsBlock;
102 |
103 | /**
104 | A block to be executed just after an object is added to the cache. This block will be excuted within
105 | a lock, i.e. all reads and writes are suspended for the duration of the block.
106 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
107 | */
108 | @property (nullable, copy) PINCacheObjectBlock didAddObjectBlock;
109 |
110 | /**
111 | A block to be executed just after an object is removed from the cache. This block will be excuted
112 | within a lock, i.e. all reads and writes are suspended for the duration of the block.
113 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
114 | */
115 | @property (nullable, copy) PINCacheObjectBlock didRemoveObjectBlock;
116 |
117 | /**
118 | A block to be executed just after all objects are removed from the cache as a result of .
119 | This block will be excuted within a lock, i.e. all reads and writes are suspended for the duration of the block.
120 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
121 | */
122 | @property (nullable, copy) PINCacheBlock didRemoveAllObjectsBlock;
123 |
124 | /**
125 | A block to be executed upon receiving a memory warning (iOS only) potentially in parallel with other blocks on the .
126 | This block will be executed regardless of the value of . Defaults to `nil`.
127 | */
128 | @property (nullable, copy) PINCacheBlock didReceiveMemoryWarningBlock;
129 |
130 | /**
131 | A block to be executed when the app enters the background (iOS only) potentially in parallel with other blocks on the .
132 | This block will be executed regardless of the value of . Defaults to `nil`.
133 | */
134 | @property (nullable, copy) PINCacheBlock didEnterBackgroundBlock;
135 |
136 | #pragma mark - Lifecycle
137 | /// @name Shared Cache
138 |
139 | /**
140 | A shared cache.
141 |
142 | @result The shared singleton cache instance.
143 | */
144 | @property (class, strong, readonly) PINMemoryCache *sharedCache;
145 |
146 | - (instancetype)initWithOperationQueue:(PINOperationQueue *)operationQueue;
147 |
148 | - (instancetype)initWithName:(NSString *)name operationQueue:(PINOperationQueue *)operationQueue NS_DESIGNATED_INITIALIZER;
149 |
150 | #pragma mark - Asynchronous Methods
151 | /// @name Asynchronous Methods
152 |
153 | /**
154 | Removes objects from the cache, costliest objects first, until the is below the specified
155 | value. This method returns immediately and executes the passed block after the cache has been trimmed,
156 | potentially in parallel with other blocks on the .
157 |
158 | @param cost The total accumulation allowed to remain after the cache has been trimmed.
159 | @param block A block to be executed concurrently after the cache has been trimmed, or nil.
160 | */
161 | - (void)trimToCostAsync:(NSUInteger)cost completion:(nullable PINCacheBlock)block;
162 |
163 | /**
164 | Removes objects from the cache, ordered by date (least recently used first), until the is below
165 | the specified value. This method returns immediately and executes the passed block after the cache has been
166 | trimmed, potentially in parallel with other blocks on the .
167 |
168 | @param cost The total accumulation allowed to remain after the cache has been trimmed.
169 | @param block A block to be executed concurrently after the cache has been trimmed, or nil.
170 | */
171 | - (void)trimToCostByDateAsync:(NSUInteger)cost completion:(nullable PINCacheBlock)block;
172 |
173 | /**
174 | Loops through all objects in the cache with reads and writes suspended. Calling serial methods which
175 | write to the cache inside block may be unsafe and may result in a deadlock. This method returns immediately.
176 |
177 | @param block A block to be executed for every object in the cache.
178 | @param completionBlock An optional block to be executed concurrently when the enumeration is complete.
179 | */
180 | - (void)enumerateObjectsWithBlockAsync:(PINCacheObjectBlock)block completionBlock:(nullable PINCacheBlock)completionBlock;
181 |
182 | #pragma mark - Synchronous Methods
183 | /// @name Synchronous Methods
184 |
185 | /**
186 | Removes objects from the cache, costliest objects first, until the is below the specified
187 | value. This method blocks the calling thread until the cache has been trimmed.
188 |
189 | @see trimToCostAsync:
190 | @param cost The total accumulation allowed to remain after the cache has been trimmed.
191 | */
192 | - (void)trimToCost:(NSUInteger)cost;
193 |
194 | /**
195 | Removes objects from the cache, ordered by date (least recently used first), until the is below
196 | the specified value. This method blocks the calling thread until the cache has been trimmed.
197 |
198 | @see trimToCostByDateAsync:
199 | @param cost The total accumulation allowed to remain after the cache has been trimmed.
200 | */
201 | - (void)trimToCostByDate:(NSUInteger)cost;
202 |
203 | /**
204 | Loops through all objects in the cache within a memory lock (reads and writes are suspended during the enumeration).
205 | This method blocks the calling thread until all objects have been enumerated.
206 | Calling synchronous methods on the cache within this callback will likely cause a deadlock.
207 |
208 | @see enumerateObjectsWithBlockAsync:completionBlock:
209 | @param block A block to be executed for every object in the cache.
210 |
211 | @warning Do not call this method within the event blocks (, etc.)
212 | Instead use the asynchronous version, .
213 |
214 | */
215 | - (void)enumerateObjectsWithBlock:(PIN_NOESCAPE PINCacheObjectBlock)block;
216 |
217 | @end
218 |
219 |
220 | #pragma mark - Deprecated
221 |
222 | typedef void (^PINMemoryCacheBlock)(PINMemoryCache *cache);
223 | typedef void (^PINMemoryCacheObjectBlock)(PINMemoryCache *cache, NSString *key, id _Nullable object);
224 | typedef void (^PINMemoryCacheContainmentBlock)(BOOL containsObject);
225 |
226 | @interface PINMemoryCache (Deprecated)
227 | - (void)containsObjectForKey:(NSString *)key block:(PINMemoryCacheContainmentBlock)block __attribute__((deprecated));
228 | - (void)objectForKey:(NSString *)key block:(nullable PINMemoryCacheObjectBlock)block __attribute__((deprecated));
229 | - (void)setObject:(id)object forKey:(NSString *)key block:(nullable PINMemoryCacheObjectBlock)block __attribute__((deprecated));
230 | - (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(nullable PINMemoryCacheObjectBlock)block __attribute__((deprecated));
231 | - (void)removeObjectForKey:(NSString *)key block:(nullable PINMemoryCacheObjectBlock)block __attribute__((deprecated));
232 | - (void)trimToDate:(NSDate *)date block:(nullable PINMemoryCacheBlock)block __attribute__((deprecated));
233 | - (void)trimToCost:(NSUInteger)cost block:(nullable PINMemoryCacheBlock)block __attribute__((deprecated));
234 | - (void)trimToCostByDate:(NSUInteger)cost block:(nullable PINMemoryCacheBlock)block __attribute__((deprecated));
235 | - (void)removeAllObjects:(nullable PINMemoryCacheBlock)block __attribute__((deprecated));
236 | - (void)enumerateObjectsWithBlock:(PINMemoryCacheObjectBlock)block completionBlock:(nullable PINMemoryCacheBlock)completionBlock __attribute__((deprecated));
237 | @end
238 |
239 | NS_ASSUME_NONNULL_END
240 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperation.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperation.h
3 | // PINOperation
4 | //
5 | // Created by Adlai Holler on 1/10/17.
6 | // Copyright © 2017 Pinterest. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import
12 | #import
13 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperationGroup.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperationGroup.h
3 | // PINQueue
4 | //
5 | // Created by Garrett Moon on 10/8/16.
6 | // Copyright © 2016 Pinterest. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "PINOperationTypes.h"
11 | #import "PINOperationMacros.h"
12 |
13 | @class PINOperationQueue;
14 |
15 | NS_ASSUME_NONNULL_BEGIN
16 |
17 | @protocol PINGroupOperationReference;
18 |
19 | PINOP_SUBCLASSING_RESTRICTED
20 | @interface PINOperationGroup : NSObject
21 |
22 | - (instancetype)init NS_UNAVAILABLE;
23 | + (instancetype)asyncOperationGroupWithQueue:(PINOperationQueue *)operationQueue;
24 |
25 | - (nullable id )addOperation:(dispatch_block_t)operation;
26 | - (nullable id )addOperation:(dispatch_block_t)operation withPriority:(PINOperationQueuePriority)priority;
27 | - (void)start;
28 | - (void)cancel;
29 | - (void)setCompletion:(dispatch_block_t)completion;
30 | - (void)waitUntilComplete;
31 |
32 | @end
33 |
34 | @protocol PINGroupOperationReference
35 |
36 | @end
37 |
38 | NS_ASSUME_NONNULL_END
39 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperationGroup.m:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperationGroup.m
3 | // PINQueue
4 | //
5 | // Created by Garrett Moon on 10/8/16.
6 | // Copyright © 2016 Pinterest. All rights reserved.
7 | //
8 |
9 | #import "PINOperationGroup.h"
10 | #import "PINOperation.h"
11 | #import
12 |
13 | @interface NSNumber (PINGroupOperationQueue)
14 | @end
15 |
16 | @interface PINOperationGroup ()
17 | {
18 | pthread_mutex_t _lock;
19 |
20 | PINOperationQueue *_operationQueue;
21 | NSMutableArray *_operations;
22 | NSMutableArray *_operationPriorities;
23 | NSMutableArray > *_operationReferences;
24 | NSMapTable , id > *_groupToOperationReferences;
25 | NSUInteger _operationReferenceCount;
26 |
27 | dispatch_group_t _group;
28 |
29 | dispatch_block_t _completion;
30 |
31 | BOOL _started;
32 | BOOL _canceled;
33 | }
34 |
35 | - (instancetype)initWithOperationQueue:(PINOperationQueue *)operationQueue NS_DESIGNATED_INITIALIZER;
36 |
37 | @end
38 |
39 | @implementation PINOperationGroup
40 |
41 | - (instancetype)initWithOperationQueue:(PINOperationQueue *)operationQueue
42 | {
43 | if (self = [super init]) {
44 | pthread_mutex_init(&_lock, NULL);
45 |
46 | _operationQueue = operationQueue;
47 |
48 | _operations = [[NSMutableArray alloc] init];
49 | _operationReferences = [[NSMutableArray alloc] init];
50 | _operationPriorities = [[NSMutableArray alloc] init];
51 |
52 | _groupToOperationReferences = [NSMapTable weakToStrongObjectsMapTable];
53 | _group = dispatch_group_create();
54 | }
55 | return self;
56 | }
57 |
58 | - (void)dealloc
59 | {
60 | pthread_mutex_destroy(&_lock);
61 | }
62 |
63 | + (instancetype)asyncOperationGroupWithQueue:(PINOperationQueue *)operationQueue
64 | {
65 | return [[self alloc] initWithOperationQueue:operationQueue];
66 | }
67 |
68 | - (id )locked_nextOperationReference
69 | {
70 | id reference = [NSNumber numberWithUnsignedInteger:++_operationReferenceCount];
71 | return reference;
72 | }
73 |
74 | - (void)start
75 | {
76 | [self lock];
77 | NSAssert(_canceled == NO, @"Operation group canceled.");
78 | if (_started == NO && _canceled == NO) {
79 | for (NSUInteger idx = 0; idx < _operations.count; idx++) {
80 | dispatch_group_enter(_group);
81 | dispatch_block_t originalOperation = _operations[idx];
82 | dispatch_block_t groupBlock = ^{
83 | originalOperation();
84 | dispatch_group_leave(_group);
85 | };
86 |
87 | id operationReference = [_operationQueue addOperation:groupBlock withPriority:[_operationPriorities[idx] unsignedIntegerValue]];
88 | [_groupToOperationReferences setObject:operationReference forKey:_operationReferences[idx]];
89 | }
90 |
91 | if (_completion) {
92 | dispatch_queue_t completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
93 | dispatch_group_notify(_group, completionQueue, ^{
94 | [self runCompletionIfNeeded];
95 | });
96 | }
97 |
98 | _operations = nil;
99 | _operationPriorities = nil;
100 | _operationReferences = nil;
101 | }
102 | [self unlock];
103 | }
104 |
105 | - (void)cancel
106 | {
107 | [self lock];
108 | _canceled = YES;
109 |
110 | for (id operationReference in [_groupToOperationReferences objectEnumerator]) {
111 | if ([_operationQueue cancelOperation:operationReference]) {
112 | dispatch_group_leave(_group);
113 | }
114 | }
115 |
116 | //TODO just nil out instead? Does it make sense to support adding operations after cancelation?
117 | [_groupToOperationReferences removeAllObjects];
118 | [_operations removeAllObjects];
119 | [_operationPriorities removeAllObjects];
120 | [_operationReferences removeAllObjects];
121 |
122 | _completion = nil;
123 | [self unlock];
124 | }
125 |
126 | - (id )addOperation:(dispatch_block_t)operation
127 | {
128 | return [self addOperation:operation withPriority:PINOperationQueuePriorityDefault];
129 | }
130 |
131 | - (id )addOperation:(dispatch_block_t)operation withPriority:(PINOperationQueuePriority)priority
132 | {
133 | [self lock];
134 | id reference = nil;
135 | NSAssert(_started == NO && _canceled == NO, @"Operation group already started or canceled.");
136 | if (_started == NO && _canceled == NO) {
137 | reference = [self locked_nextOperationReference];
138 | [_operations addObject:operation];
139 | [_operationPriorities addObject:@(priority)];
140 | [_operationReferences addObject:reference];
141 | }
142 | [self unlock];
143 |
144 | return reference;
145 | }
146 |
147 | - (void)setCompletion:(dispatch_block_t)completion
148 | {
149 | [self lock];
150 | NSAssert(_started == NO && _canceled == NO, @"Operation group already started or canceled.");
151 | if (_started == NO && _canceled == NO) {
152 | _completion = completion;
153 | }
154 | [self unlock];
155 | }
156 |
157 | - (void)waitUntilComplete
158 | {
159 | [self start];
160 | dispatch_group_wait(_group, DISPATCH_TIME_FOREVER);
161 | [self runCompletionIfNeeded];
162 | }
163 |
164 | - (void)runCompletionIfNeeded
165 | {
166 | dispatch_block_t completion;
167 | [self lock];
168 | completion = _completion;
169 | _completion = nil;
170 | [self unlock];
171 |
172 | if (completion) {
173 | completion();
174 | }
175 | }
176 |
177 | - (void)lock
178 | {
179 | pthread_mutex_lock(&_lock);
180 | }
181 |
182 | - (void)unlock
183 | {
184 | pthread_mutex_unlock(&_lock);
185 | }
186 |
187 | @end
188 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperationMacros.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperationMacros.h
3 | // PINOperation
4 | //
5 | // Created by Adlai Holler on 1/10/17.
6 | // Copyright © 2017 Pinterest. All rights reserved.
7 | //
8 |
9 | #ifndef PINOP_SUBCLASSING_RESTRICTED
10 | #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
11 | #define PINOP_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted))
12 | #else
13 | #define PINOP_SUBCLASSING_RESTRICTED
14 | #endif // #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
15 | #endif // #ifndef PINOP_SUBCLASSING_RESTRICTED
16 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperationQueue.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperationQueue.h
3 | // Pods
4 | //
5 | // Created by Garrett Moon on 8/23/16.
6 | //
7 | //
8 |
9 | #import
10 | #import "PINOperationTypes.h"
11 | #import "PINOperationMacros.h"
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | typedef void(^PINOperationBlock)(id _Nullable data);
16 | typedef _Nullable id(^PINOperationDataCoalescingBlock)(id _Nullable existingData, id _Nullable newData);
17 |
18 | @protocol PINOperationReference;
19 |
20 | PINOP_SUBCLASSING_RESTRICTED
21 | @interface PINOperationQueue : NSObject
22 |
23 | - (instancetype)init NS_UNAVAILABLE;
24 |
25 | /**
26 | * Initializes and returns a newly allocated operation queue with the specified number of maximum concurrent operations.
27 | *
28 | * @param maxConcurrentOperations The maximum number of queued operations that can execute at the same time.
29 | *
30 | */
31 | - (instancetype)initWithMaxConcurrentOperations:(NSUInteger)maxConcurrentOperations;
32 |
33 | /**
34 | * Initializes and returns a newly allocated operation queue with the specified number of maximum concurrent operations and the concurrent queue they will be scheduled on.
35 | *
36 | * @param maxConcurrentOperations The maximum number of queued operations that can execute at the same time.
37 | * @param concurrentQueue The operation queue to schedule concurrent operations
38 | *
39 | */
40 | - (instancetype)initWithMaxConcurrentOperations:(NSUInteger)maxConcurrentOperations concurrentQueue:(dispatch_queue_t)concurrentQueue NS_DESIGNATED_INITIALIZER;
41 |
42 | /**
43 | * Returns the shared instance of the PINOperationQueue class.
44 | */
45 | + (instancetype)sharedOperationQueue;
46 |
47 | /**
48 | * Adds the specified operation object to the receiver.
49 | *
50 | * @param operation The operation object to be added to the queue.
51 | *
52 | */
53 | - (id )addOperation:(dispatch_block_t)operation;
54 |
55 | /**
56 | * Adds the specified operation object to the receiver.
57 | *
58 | * @param operation The operation object to be added to the queue.
59 | * @param priority The execution priority of the operation in an operation queue.
60 | *
61 | * @discussion
62 | */
63 | - (id )addOperation:(dispatch_block_t)operation withPriority:(PINOperationQueuePriority)priority;
64 |
65 | /**
66 | * Adds the specified operation object to the receiver.
67 | *
68 | * @param operation The operation object to be added to the queue.
69 | * @param priority The execution priority of the operation in an operation queue.
70 | * @param identifier A string that identifies the operations that can be coalesced.
71 | * @param coalescingData The optional data consumed by this operation that needs to be updated/coalesced with data of a new operation when coalescing the two operations happens.
72 | * @param dataCoalescingBlock The optional block called to update/coalesce the data of this operation with data of a new operation when coalescing the two operations happens.
73 | * @param completion The block to execute after the operation finished.
74 | *
75 | * @discussion
76 | */
77 | - (id )addOperation:(PINOperationBlock)operation
78 | withPriority:(PINOperationQueuePriority)priority
79 | identifier:(nullable NSString *)identifier
80 | coalescingData:(nullable id)coalescingData
81 | dataCoalescingBlock:(nullable PINOperationDataCoalescingBlock)dataCoalescingBlock
82 | completion:(nullable dispatch_block_t)completion;
83 |
84 | /**
85 | * The maximum number of queued operations that can execute at the same time.
86 | *
87 | * @discussion The value in this property affects only the operations that the current queue has executing at the same time. Other operation queues can also execute their maximum number of operations in parallel.
88 | * Reducing the number of concurrent operations does not affect any operations that are currently executing.
89 | *
90 | * Setting this value to 1 the operations will not be processed by priority as the operations will processed in a FIFO order to prevent deadlocks if operations depend on certain other operations to run in order.
91 | *
92 | */
93 | @property (assign) NSUInteger maxConcurrentOperations;
94 |
95 | /**
96 | * Marks the operation as cancelled
97 | */
98 | - (BOOL)cancelOperation:(id )operationReference;
99 |
100 | /**
101 | * Cancels all queued operations
102 | */
103 | - (void)cancelAllOperations;
104 |
105 | /**
106 | * Blocks the current thread until all of the receiver’s queued and executing operations finish executing.
107 | *
108 | * @discussion When called, this method blocks the current thread and waits for the receiver’s current and queued
109 | * operations to finish executing. While the current thread is blocked, the receiver continues to launch already
110 | * queued operations and monitor those that are executing.
111 | *
112 | * @warning This should never be called from within an operation submitted to the PINOperationQueue as this will result
113 | * in a deadlock.
114 | */
115 | - (void)waitUntilAllOperationsAreFinished;
116 |
117 | /**
118 | * Sets the priority for a operation via it's reference
119 | *
120 | * @param priority The new priority for the operation
121 | * @param reference The reference for the operation
122 | *
123 | */
124 | - (void)setOperationPriority:(PINOperationQueuePriority)priority withReference:(id )reference;
125 |
126 | @end
127 |
128 | @protocol PINOperationReference
129 |
130 | @end
131 |
132 | NS_ASSUME_NONNULL_END
133 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperationQueue.m:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperationQueue.m
3 | // Pods
4 | //
5 | // Created by Garrett Moon on 8/23/16.
6 | //
7 | //
8 |
9 | #import "PINOperationQueue.h"
10 | #import
11 |
12 | @class PINOperation;
13 |
14 | @interface NSNumber (PINOperationQueue)
15 |
16 | @end
17 |
18 | @interface PINOperationQueue () {
19 | pthread_mutex_t _lock;
20 | //increments with every operation to allow cancelation
21 | NSUInteger _operationReferenceCount;
22 | NSUInteger _maxConcurrentOperations;
23 |
24 | dispatch_group_t _group;
25 |
26 | dispatch_queue_t _serialQueue;
27 | BOOL _serialQueueBusy;
28 |
29 | dispatch_semaphore_t _concurrentSemaphore;
30 | dispatch_queue_t _concurrentQueue;
31 | dispatch_queue_t _semaphoreQueue;
32 |
33 | NSMutableOrderedSet *_queuedOperations;
34 | NSMutableOrderedSet *_lowPriorityOperations;
35 | NSMutableOrderedSet *_defaultPriorityOperations;
36 | NSMutableOrderedSet *_highPriorityOperations;
37 |
38 | NSMapTable, PINOperation *> *_referenceToOperations;
39 | NSMapTable *_identifierToOperations;
40 | }
41 |
42 | @end
43 |
44 | @interface PINOperation : NSObject
45 |
46 | @property (nonatomic, strong) PINOperationBlock block;
47 | @property (nonatomic, strong) id reference;
48 | @property (nonatomic, assign) PINOperationQueuePriority priority;
49 | @property (nonatomic, strong) NSMutableArray *completions;
50 | @property (nonatomic, strong) NSString *identifier;
51 | @property (nonatomic, strong) id data;
52 |
53 | + (instancetype)operationWithBlock:(PINOperationBlock)block reference:(id )reference priority:(PINOperationQueuePriority)priority identifier:(nullable NSString *)identifier data:(nullable id)data completion:(nullable dispatch_block_t)completion;
54 |
55 | - (void)addCompletion:(nullable dispatch_block_t)completion;
56 |
57 | @end
58 |
59 | @implementation PINOperation
60 |
61 | + (instancetype)operationWithBlock:(PINOperationBlock)block reference:(id)reference priority:(PINOperationQueuePriority)priority identifier:(NSString *)identifier data:(id)data completion:(dispatch_block_t)completion
62 | {
63 | PINOperation *operation = [[self alloc] init];
64 | operation.block = block;
65 | operation.reference = reference;
66 | operation.priority = priority;
67 | operation.identifier = identifier;
68 | operation.data = data;
69 | [operation addCompletion:completion];
70 |
71 | return operation;
72 | }
73 |
74 | - (void)addCompletion:(dispatch_block_t)completion
75 | {
76 | if (completion == nil) {
77 | return;
78 | }
79 | if (_completions == nil) {
80 | _completions = [NSMutableArray array];
81 | }
82 | [_completions addObject:completion];
83 | }
84 |
85 | @end
86 |
87 | @implementation PINOperationQueue
88 |
89 | - (instancetype)initWithMaxConcurrentOperations:(NSUInteger)maxConcurrentOperations
90 | {
91 | return [self initWithMaxConcurrentOperations:maxConcurrentOperations concurrentQueue:dispatch_queue_create("PINOperationQueue Concurrent Queue", DISPATCH_QUEUE_CONCURRENT)];
92 | }
93 |
94 | - (instancetype)initWithMaxConcurrentOperations:(NSUInteger)maxConcurrentOperations concurrentQueue:(dispatch_queue_t)concurrentQueue
95 | {
96 | if (self = [super init]) {
97 | NSAssert(maxConcurrentOperations > 0, @"Max concurrent operations must be greater than 0.");
98 | _maxConcurrentOperations = maxConcurrentOperations;
99 | _operationReferenceCount = 0;
100 |
101 | pthread_mutexattr_t attr;
102 | pthread_mutexattr_init(&attr);
103 | //mutex must be recursive to allow scheduling of operations from within operations
104 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
105 | pthread_mutex_init(&_lock, &attr);
106 |
107 | _group = dispatch_group_create();
108 |
109 | _serialQueue = dispatch_queue_create("PINOperationQueue Serial Queue", DISPATCH_QUEUE_SERIAL);
110 |
111 | _concurrentQueue = concurrentQueue;
112 |
113 | //Create a queue with max - 1 because this plus the serial queue add up to max.
114 | _concurrentSemaphore = dispatch_semaphore_create(_maxConcurrentOperations - 1);
115 | _semaphoreQueue = dispatch_queue_create("PINOperationQueue Serial Semaphore Queue", DISPATCH_QUEUE_SERIAL);
116 |
117 | _queuedOperations = [[NSMutableOrderedSet alloc] init];
118 | _lowPriorityOperations = [[NSMutableOrderedSet alloc] init];
119 | _defaultPriorityOperations = [[NSMutableOrderedSet alloc] init];
120 | _highPriorityOperations = [[NSMutableOrderedSet alloc] init];
121 |
122 | _referenceToOperations = [NSMapTable weakToWeakObjectsMapTable];
123 | _identifierToOperations = [NSMapTable weakToWeakObjectsMapTable];
124 | }
125 | return self;
126 | }
127 |
128 | - (void)dealloc
129 | {
130 | pthread_mutex_destroy(&_lock);
131 | }
132 |
133 | + (instancetype)sharedOperationQueue
134 | {
135 | static PINOperationQueue *sharedOperationQueue = nil;
136 | static dispatch_once_t onceToken;
137 | dispatch_once(&onceToken, ^{
138 | sharedOperationQueue = [[PINOperationQueue alloc] initWithMaxConcurrentOperations:MAX([[NSProcessInfo processInfo] activeProcessorCount], 2)];
139 | });
140 | return sharedOperationQueue;
141 | }
142 |
143 | - (id )nextOperationReference
144 | {
145 | [self lock];
146 | id reference = [NSNumber numberWithUnsignedInteger:++_operationReferenceCount];
147 | [self unlock];
148 | return reference;
149 | }
150 |
151 | - (id )addOperation:(dispatch_block_t)block
152 | {
153 | return [self addOperation:block withPriority:PINOperationQueuePriorityDefault];
154 | }
155 |
156 | - (id )addOperation:(dispatch_block_t)block withPriority:(PINOperationQueuePriority)priority
157 | {
158 | PINOperation *operation = [PINOperation operationWithBlock:^(id data) { block(); }
159 | reference:[self nextOperationReference]
160 | priority:priority
161 | identifier:nil
162 | data:nil
163 | completion:nil];
164 | [self lock];
165 | [self locked_addOperation:operation];
166 | [self unlock];
167 |
168 | [self scheduleNextOperations:NO];
169 |
170 | return operation.reference;
171 | }
172 |
173 | - (id)addOperation:(PINOperationBlock)block
174 | withPriority:(PINOperationQueuePriority)priority
175 | identifier:(NSString *)identifier
176 | coalescingData:(id)coalescingData
177 | dataCoalescingBlock:(PINOperationDataCoalescingBlock)dataCoalescingBlock
178 | completion:(dispatch_block_t)completion
179 | {
180 | id reference = nil;
181 | BOOL isNewOperation = NO;
182 |
183 | [self lock];
184 | PINOperation *operation = nil;
185 | if (identifier != nil && (operation = [_identifierToOperations objectForKey:identifier]) != nil) {
186 | // There is an exisiting operation with the provided identifier, let's coalesce these operations
187 | if (dataCoalescingBlock != nil) {
188 | operation.data = dataCoalescingBlock(operation.data, coalescingData);
189 | }
190 |
191 | [operation addCompletion:completion];
192 | } else {
193 | isNewOperation = YES;
194 | operation = [PINOperation operationWithBlock:block
195 | reference:[self nextOperationReference]
196 | priority:priority
197 | identifier:identifier
198 | data:coalescingData
199 | completion:completion];
200 | [self locked_addOperation:operation];
201 | }
202 | reference = operation.reference;
203 | [self unlock];
204 |
205 | if (isNewOperation) {
206 | [self scheduleNextOperations:NO];
207 | }
208 |
209 | return reference;
210 | }
211 |
212 | - (void)locked_addOperation:(PINOperation *)operation
213 | {
214 | NSMutableOrderedSet *queue = [self operationQueueWithPriority:operation.priority];
215 |
216 | dispatch_group_enter(_group);
217 | [queue addObject:operation];
218 | [_queuedOperations addObject:operation];
219 | [_referenceToOperations setObject:operation forKey:operation.reference];
220 | if (operation.identifier != nil) {
221 | [_identifierToOperations setObject:operation forKey:operation.identifier];
222 | }
223 | }
224 |
225 | - (void)cancelAllOperations
226 | {
227 | [self lock];
228 | for (PINOperation *operation in [[_referenceToOperations copy] objectEnumerator]) {
229 | [self locked_cancelOperation:operation.reference];
230 | }
231 | [self unlock];
232 | }
233 |
234 |
235 | - (BOOL)cancelOperation:(id )operationReference
236 | {
237 | [self lock];
238 | BOOL success = [self locked_cancelOperation:operationReference];
239 | [self unlock];
240 | return success;
241 | }
242 |
243 | - (NSUInteger)maxConcurrentOperations
244 | {
245 | [self lock];
246 | NSUInteger maxConcurrentOperations = _maxConcurrentOperations;
247 | [self unlock];
248 | return maxConcurrentOperations;
249 | }
250 |
251 | - (void)setMaxConcurrentOperations:(NSUInteger)maxConcurrentOperations
252 | {
253 | NSAssert(maxConcurrentOperations > 0, @"Max concurrent operations must be greater than 0.");
254 | [self lock];
255 | __block NSInteger difference = maxConcurrentOperations - _maxConcurrentOperations;
256 | _maxConcurrentOperations = maxConcurrentOperations;
257 | [self unlock];
258 |
259 | if (difference == 0) {
260 | return;
261 | }
262 |
263 | dispatch_async(_semaphoreQueue, ^{
264 | while (difference != 0) {
265 | if (difference > 0) {
266 | dispatch_semaphore_signal(_concurrentSemaphore);
267 | difference--;
268 | } else {
269 | dispatch_semaphore_wait(_concurrentSemaphore, DISPATCH_TIME_FOREVER);
270 | difference++;
271 | }
272 | }
273 | });
274 | }
275 |
276 | #pragma mark - private methods
277 |
278 | - (BOOL)locked_cancelOperation:(id )operationReference
279 | {
280 | BOOL success = NO;
281 | PINOperation *operation = [_referenceToOperations objectForKey:operationReference];
282 | if (operation) {
283 | NSMutableOrderedSet *queue = [self operationQueueWithPriority:operation.priority];
284 | if ([queue containsObject:operation]) {
285 | success = YES;
286 | [queue removeObject:operation];
287 | [_queuedOperations removeObject:operation];
288 | dispatch_group_leave(_group);
289 | }
290 | }
291 | return success;
292 | }
293 |
294 | - (void)setOperationPriority:(PINOperationQueuePriority)priority withReference:(id )operationReference
295 | {
296 | [self lock];
297 | PINOperation *operation = [_referenceToOperations objectForKey:operationReference];
298 | if (operation && operation.priority != priority) {
299 | NSMutableOrderedSet *oldQueue = [self operationQueueWithPriority:operation.priority];
300 | [oldQueue removeObject:operation];
301 |
302 | operation.priority = priority;
303 |
304 | NSMutableOrderedSet *queue = [self operationQueueWithPriority:priority];
305 | [queue addObject:operation];
306 | }
307 | [self unlock];
308 | }
309 |
310 | /**
311 | Schedule next operations schedules the next operation by queue order onto the serial queue if
312 | it's available and one operation by priority order onto the concurrent queue.
313 | */
314 | - (void)scheduleNextOperations:(BOOL)onlyCheckSerial
315 | {
316 | [self lock];
317 |
318 | //get next available operation in order, ignoring priority and run it on the serial queue
319 | if (_serialQueueBusy == NO) {
320 | PINOperation *operation = [self locked_nextOperationByQueue];
321 | if (operation) {
322 | _serialQueueBusy = YES;
323 | dispatch_async(_serialQueue, ^{
324 | operation.block(operation.data);
325 | for (dispatch_block_t completion in operation.completions) {
326 | completion();
327 | }
328 | dispatch_group_leave(_group);
329 |
330 | [self lock];
331 | _serialQueueBusy = NO;
332 | [self unlock];
333 |
334 | //see if there are any other operations
335 | [self scheduleNextOperations:YES];
336 | });
337 | }
338 | }
339 |
340 | NSInteger maxConcurrentOperations = _maxConcurrentOperations;
341 |
342 | [self unlock];
343 |
344 | if (onlyCheckSerial) {
345 | return;
346 | }
347 |
348 | //if only one concurrent operation is set, let's just use the serial queue for executing it
349 | if (maxConcurrentOperations < 2) {
350 | return;
351 | }
352 |
353 | dispatch_async(_semaphoreQueue, ^{
354 | dispatch_semaphore_wait(_concurrentSemaphore, DISPATCH_TIME_FOREVER);
355 | [self lock];
356 | PINOperation *operation = [self locked_nextOperationByPriority];
357 | [self unlock];
358 |
359 | if (operation) {
360 | dispatch_async(_concurrentQueue, ^{
361 | operation.block(operation.data);
362 | for (dispatch_block_t completion in operation.completions) {
363 | completion();
364 | }
365 | dispatch_group_leave(_group);
366 | dispatch_semaphore_signal(_concurrentSemaphore);
367 | });
368 | } else {
369 | dispatch_semaphore_signal(_concurrentSemaphore);
370 | }
371 | });
372 | }
373 |
374 | - (NSMutableOrderedSet *)operationQueueWithPriority:(PINOperationQueuePriority)priority
375 | {
376 | switch (priority) {
377 | case PINOperationQueuePriorityLow:
378 | return _lowPriorityOperations;
379 |
380 | case PINOperationQueuePriorityDefault:
381 | return _defaultPriorityOperations;
382 |
383 | case PINOperationQueuePriorityHigh:
384 | return _highPriorityOperations;
385 |
386 | default:
387 | NSAssert(NO, @"Invalid priority set");
388 | return _defaultPriorityOperations;
389 | }
390 | }
391 |
392 | //Call with lock held
393 | - (PINOperation *)locked_nextOperationByPriority
394 | {
395 | PINOperation *operation = [_highPriorityOperations firstObject];
396 | if (operation == nil) {
397 | operation = [_defaultPriorityOperations firstObject];
398 | }
399 | if (operation == nil) {
400 | operation = [_lowPriorityOperations firstObject];
401 | }
402 | if (operation) {
403 | [self locked_removeOperation:operation];
404 | }
405 | return operation;
406 | }
407 |
408 | //Call with lock held
409 | - (PINOperation *)locked_nextOperationByQueue
410 | {
411 | PINOperation *operation = [_queuedOperations firstObject];
412 | [self locked_removeOperation:operation];
413 | return operation;
414 | }
415 |
416 | - (void)waitUntilAllOperationsAreFinished
417 | {
418 | [self scheduleNextOperations:NO];
419 | dispatch_group_wait(_group, DISPATCH_TIME_FOREVER);
420 | }
421 |
422 | //Call with lock held
423 | - (void)locked_removeOperation:(PINOperation *)operation
424 | {
425 | if (operation) {
426 | NSMutableOrderedSet *priorityQueue = [self operationQueueWithPriority:operation.priority];
427 | [priorityQueue removeObject:operation];
428 | [_queuedOperations removeObject:operation];
429 | }
430 | }
431 |
432 | - (void)lock
433 | {
434 | pthread_mutex_lock(&_lock);
435 | }
436 |
437 | - (void)unlock
438 | {
439 | pthread_mutex_unlock(&_lock);
440 | }
441 |
442 | @end
443 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/PINCache/PINOperation/PINOperationTypes.h:
--------------------------------------------------------------------------------
1 | //
2 | // PINOperationTypes.h
3 | // PINOperation
4 | //
5 | // Created by Adlai Holler on 1/10/17.
6 | // Copyright © 2017 Pinterest. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef NS_ENUM(NSUInteger, PINOperationQueuePriority) {
12 | PINOperationQueuePriorityLow,
13 | PINOperationQueuePriorityDefault,
14 | PINOperationQueuePriorityHigh,
15 | };
16 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/YYThreadSafeDictionary/YYThreadSafeDictionary.h:
--------------------------------------------------------------------------------
1 | //
2 | // YYThreadSafeDictionary.h
3 | // YYKit
4 | //
5 | // Created by ibireme on 14/10/21.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import
13 |
14 | /**
15 | @warning OSSpinLock is not safe anymore...
16 | */
17 |
18 | /**
19 | A simple implementation of thread safe mutable dictionary.
20 |
21 | @discussion Generally, access performance is lower than NSMutableDictionary,
22 | but higher than using @synchronized, NSLock, or pthread_mutex_t.
23 |
24 | @discussion It's also compatible with the custom methods in `NSDictionary(YYAdd)`
25 | and `NSMutableDictionary(YYAdd)`
26 |
27 | @warning Fast enumerate(for...in) and enumerator is not thread safe,
28 | use enumerate using block instead. When enumerate or sort with block/callback,
29 | do *NOT* send message to the dictionary inside the block/callback.
30 | */
31 | @interface YYThreadSafeDictionary : NSMutableDictionary
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/YYThreadSafeDictionary/YYThreadSafeDictionary.m:
--------------------------------------------------------------------------------
1 | //
2 | // YYThreadSafeDictionary.m
3 | // YYKit
4 | //
5 | // Created by ibireme on 14/10/21.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import "YYThreadSafeDictionary.h"
13 | #import
14 |
15 |
16 | #define INIT(...) self = super.init; \
17 | if (!self) return nil; \
18 | __VA_ARGS__; \
19 | if (!_dic) return nil; \
20 | _lock = OS_SPINLOCK_INIT; \
21 | return self;
22 |
23 |
24 | #define LOCK(...) OSSpinLockLock(&_lock); \
25 | __VA_ARGS__; \
26 | OSSpinLockUnlock(&_lock);
27 |
28 |
29 | @implementation YYThreadSafeDictionary {
30 | NSMutableDictionary *_dic; //Subclass a class cluster...
31 | OSSpinLock _lock;
32 | }
33 |
34 | #pragma mark - init
35 |
36 | - (instancetype)init {
37 | INIT(_dic = [[NSMutableDictionary alloc] init]);
38 | }
39 |
40 | - (instancetype)initWithObjects:(NSArray *)objects forKeys:(NSArray *)keys {
41 | INIT(_dic = [[NSMutableDictionary alloc] initWithObjects:objects forKeys:keys]);
42 | }
43 |
44 | - (instancetype)initWithCapacity:(NSUInteger)capacity {
45 | INIT(_dic = [[NSMutableDictionary alloc] initWithCapacity:capacity]);
46 | }
47 |
48 | - (instancetype)initWithObjects:(const id[])objects forKeys:(const id [])keys count:(NSUInteger)cnt {
49 | INIT(_dic = [[NSMutableDictionary alloc] initWithObjects:objects forKeys:keys count:cnt]);
50 | }
51 |
52 | - (instancetype)initWithDictionary:(NSDictionary *)otherDictionary {
53 | INIT(_dic = [[NSMutableDictionary alloc] initWithDictionary:otherDictionary]);
54 | }
55 |
56 | - (instancetype)initWithDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL)flag {
57 | INIT(_dic = [[NSMutableDictionary alloc] initWithDictionary:otherDictionary copyItems:flag]);
58 | }
59 |
60 |
61 | #pragma mark - method
62 |
63 | - (NSUInteger)count {
64 | LOCK(NSUInteger c = _dic.count); return c;
65 | }
66 |
67 | - (id)objectForKey:(id)aKey {
68 | LOCK(id o = [_dic objectForKey:aKey]); return o;
69 | }
70 |
71 | - (NSEnumerator *)keyEnumerator {
72 | LOCK(NSEnumerator * e = [_dic keyEnumerator]); return e;
73 | }
74 |
75 | - (NSArray *)allKeys {
76 | LOCK(NSArray * a = [_dic allKeys]); return a;
77 | }
78 |
79 | - (NSArray *)allKeysForObject:(id)anObject {
80 | LOCK(NSArray * a = [_dic allKeysForObject:anObject]); return a;
81 | }
82 |
83 | - (NSArray *)allValues {
84 | LOCK(NSArray * a = [_dic allValues]); return a;
85 | }
86 |
87 | - (NSString *)description {
88 | LOCK(NSString * d = [_dic description]); return d;
89 | }
90 |
91 | - (NSString *)descriptionInStringsFileFormat {
92 | LOCK(NSString * d = [_dic descriptionInStringsFileFormat]); return d;
93 | }
94 |
95 | - (NSString *)descriptionWithLocale:(id)locale {
96 | LOCK(NSString * d = [_dic descriptionWithLocale:locale]); return d;
97 | }
98 |
99 | - (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level {
100 | LOCK(NSString * d = [_dic descriptionWithLocale:locale indent:level]); return d;
101 | }
102 |
103 | - (BOOL)isEqualToDictionary:(NSDictionary *)otherDictionary {
104 | if (otherDictionary == self) return YES;
105 |
106 | if ([otherDictionary isKindOfClass:YYThreadSafeDictionary.class]) {
107 | YYThreadSafeDictionary *other = (id)otherDictionary;
108 | BOOL isEqual;
109 | OSSpinLockLock(&_lock);
110 | OSSpinLockLock(&other->_lock);
111 | isEqual = [_dic isEqual:other->_dic];
112 | OSSpinLockUnlock(&other->_lock);
113 | OSSpinLockUnlock(&_lock);
114 | return isEqual;
115 | }
116 | return NO;
117 | }
118 |
119 | - (NSEnumerator *)objectEnumerator {
120 | LOCK(NSEnumerator * e = [_dic objectEnumerator]); return e;
121 | }
122 |
123 | - (NSArray *)objectsForKeys:(NSArray *)keys notFoundMarker:(id)marker {
124 | LOCK(NSArray * a = [_dic objectsForKeys:keys notFoundMarker:marker]); return a;
125 | }
126 |
127 | - (NSArray *)keysSortedByValueUsingSelector:(SEL)comparator {
128 | LOCK(NSArray * a = [_dic keysSortedByValueUsingSelector:comparator]); return a;
129 | }
130 |
131 | - (void)getObjects:(id __unsafe_unretained[])objects andKeys:(id __unsafe_unretained[])keys {
132 | LOCK([_dic getObjects:objects andKeys:keys]);
133 | }
134 |
135 | - (id)objectForKeyedSubscript:(id)key {
136 | LOCK(id o = [_dic objectForKeyedSubscript:key]); return o;
137 | }
138 |
139 | - (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block {
140 | LOCK([_dic enumerateKeysAndObjectsUsingBlock:block]);
141 | }
142 |
143 | - (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id key, id obj, BOOL *stop))block {
144 | LOCK([_dic enumerateKeysAndObjectsWithOptions:opts usingBlock:block]);
145 | }
146 |
147 | - (NSArray *)keysSortedByValueUsingComparator:(NSComparator)cmptr {
148 | LOCK(NSArray * a = [_dic keysSortedByValueUsingComparator:cmptr]); return a;
149 | }
150 |
151 | - (NSArray *)keysSortedByValueWithOptions:(NSSortOptions)opts usingComparator:(NSComparator)cmptr {
152 | LOCK(NSArray * a = [_dic keysSortedByValueWithOptions:opts usingComparator:cmptr]); return a;
153 | }
154 |
155 | - (NSSet *)keysOfEntriesPassingTest:(BOOL (^)(id key, id obj, BOOL *stop))predicate {
156 | LOCK(NSSet * a = [_dic keysOfEntriesPassingTest:predicate]); return a;
157 | }
158 |
159 | - (NSSet *)keysOfEntriesWithOptions:(NSEnumerationOptions)opts passingTest:(BOOL (^)(id key, id obj, BOOL *stop))predicate {
160 | LOCK(NSSet * a = [_dic keysOfEntriesWithOptions:opts passingTest:predicate]); return a;
161 | }
162 |
163 | #pragma mark - mutable
164 |
165 | - (void)removeObjectForKey:(id)aKey {
166 | LOCK([_dic removeObjectForKey:aKey]);
167 | }
168 |
169 | - (void)setObject:(id)anObject forKey:(id )aKey {
170 | LOCK([_dic setObject:anObject forKey:aKey]);
171 | }
172 |
173 | - (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary {
174 | LOCK([_dic addEntriesFromDictionary:otherDictionary]);
175 | }
176 |
177 | - (void)removeAllObjects {
178 | LOCK([_dic removeAllObjects]);
179 | }
180 |
181 | - (void)removeObjectsForKeys:(NSArray *)keyArray {
182 | LOCK([_dic removeObjectsForKeys:keyArray]);
183 | }
184 |
185 | - (void)setDictionary:(NSDictionary *)otherDictionary {
186 | LOCK([_dic setDictionary:otherDictionary]);
187 | }
188 |
189 | - (void)setObject:(id)obj forKeyedSubscript:(id )key {
190 | LOCK([_dic setObject:obj forKeyedSubscript:key]);
191 | }
192 |
193 | #pragma mark - protocol
194 |
195 | - (id)copyWithZone:(NSZone *)zone {
196 | return [self mutableCopyWithZone:zone];
197 | }
198 |
199 | - (id)mutableCopyWithZone:(NSZone *)zone {
200 | LOCK(id copiedDictionary = [[self.class allocWithZone:zone] initWithDictionary:_dic]);
201 | return copiedDictionary;
202 | }
203 |
204 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
205 | objects:(id __unsafe_unretained[])stackbuf
206 | count:(NSUInteger)len {
207 | LOCK(NSUInteger count = [_dic countByEnumeratingWithState:state objects:stackbuf count:len]);
208 | return count;
209 | }
210 |
211 | - (BOOL)isEqual:(id)object {
212 | if (object == self) return YES;
213 |
214 | if ([object isKindOfClass:YYThreadSafeDictionary.class]) {
215 | YYThreadSafeDictionary *other = object;
216 | BOOL isEqual;
217 | OSSpinLockLock(&_lock);
218 | OSSpinLockLock(&other->_lock);
219 | isEqual = [_dic isEqual:other->_dic];
220 | OSSpinLockUnlock(&other->_lock);
221 | OSSpinLockUnlock(&_lock);
222 | return isEqual;
223 | }
224 | return NO;
225 | }
226 |
227 | - (NSUInteger)hash {
228 | LOCK(NSUInteger hash = [_dic hash]);
229 | return hash;
230 | }
231 |
232 | @end
233 |
--------------------------------------------------------------------------------
/Benchmark/Vendor/version.txt:
--------------------------------------------------------------------------------
1 | PINCache: 3.0.1 Beta 5
2 | SQLite: 3.19.03.00
3 |
--------------------------------------------------------------------------------
/Framework/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.4
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Framework/YYCache.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | D9F591EB1F05472F00769742 /* YYCache.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F591E31F05472F00769742 /* YYCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | D9F591EC1F05472F00769742 /* YYCache.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F591E41F05472F00769742 /* YYCache.m */; };
12 | D9F591ED1F05472F00769742 /* YYDiskCache.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F591E51F05472F00769742 /* YYDiskCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
13 | D9F591EE1F05472F00769742 /* YYDiskCache.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F591E61F05472F00769742 /* YYDiskCache.m */; };
14 | D9F591EF1F05472F00769742 /* YYKVStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F591E71F05472F00769742 /* YYKVStorage.h */; settings = {ATTRIBUTES = (Public, ); }; };
15 | D9F591F01F05472F00769742 /* YYKVStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F591E81F05472F00769742 /* YYKVStorage.m */; };
16 | D9F591F11F05472F00769742 /* YYMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F591E91F05472F00769742 /* YYMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
17 | D9F591F21F05472F00769742 /* YYMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F591EA1F05472F00769742 /* YYMemoryCache.m */; };
18 | D9F591F51F05474100769742 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D9F591F41F05474100769742 /* libsqlite3.tbd */; };
19 | D9F591F81F05477500769742 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9F591F71F05477500769742 /* CoreFoundation.framework */; };
20 | D9F591FA1F05477B00769742 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9F591F91F05477B00769742 /* UIKit.framework */; };
21 | D9F591FC1F05478400769742 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9F591FB1F05478400769742 /* QuartzCore.framework */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | D93C45AC1F05445A009F80F9 /* YYCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YYCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
26 | D93C45B01F05445A009F80F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
27 | D9F591E31F05472F00769742 /* YYCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYCache.h; sourceTree = ""; };
28 | D9F591E41F05472F00769742 /* YYCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYCache.m; sourceTree = ""; };
29 | D9F591E51F05472F00769742 /* YYDiskCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYDiskCache.h; sourceTree = ""; };
30 | D9F591E61F05472F00769742 /* YYDiskCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYDiskCache.m; sourceTree = ""; };
31 | D9F591E71F05472F00769742 /* YYKVStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYKVStorage.h; sourceTree = ""; };
32 | D9F591E81F05472F00769742 /* YYKVStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYKVStorage.m; sourceTree = ""; };
33 | D9F591E91F05472F00769742 /* YYMemoryCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYMemoryCache.h; sourceTree = ""; };
34 | D9F591EA1F05472F00769742 /* YYMemoryCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYMemoryCache.m; sourceTree = ""; };
35 | D9F591F41F05474100769742 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
36 | D9F591F71F05477500769742 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
37 | D9F591F91F05477B00769742 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
38 | D9F591FB1F05478400769742 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
39 | /* End PBXFileReference section */
40 |
41 | /* Begin PBXFrameworksBuildPhase section */
42 | D93C45A81F05445A009F80F9 /* Frameworks */ = {
43 | isa = PBXFrameworksBuildPhase;
44 | buildActionMask = 2147483647;
45 | files = (
46 | D9F591FC1F05478400769742 /* QuartzCore.framework in Frameworks */,
47 | D9F591FA1F05477B00769742 /* UIKit.framework in Frameworks */,
48 | D9F591F81F05477500769742 /* CoreFoundation.framework in Frameworks */,
49 | D9F591F51F05474100769742 /* libsqlite3.tbd in Frameworks */,
50 | );
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXFrameworksBuildPhase section */
54 |
55 | /* Begin PBXGroup section */
56 | D93C45A21F05445A009F80F9 = {
57 | isa = PBXGroup;
58 | children = (
59 | D9F591E21F05472F00769742 /* YYCache */,
60 | D9F591F61F05477500769742 /* Frameworks */,
61 | D93C45AD1F05445A009F80F9 /* Products */,
62 | );
63 | sourceTree = "";
64 | };
65 | D93C45AD1F05445A009F80F9 /* Products */ = {
66 | isa = PBXGroup;
67 | children = (
68 | D93C45AC1F05445A009F80F9 /* YYCache.framework */,
69 | );
70 | name = Products;
71 | sourceTree = "";
72 | };
73 | D9F591E21F05472F00769742 /* YYCache */ = {
74 | isa = PBXGroup;
75 | children = (
76 | D9F591E31F05472F00769742 /* YYCache.h */,
77 | D9F591E41F05472F00769742 /* YYCache.m */,
78 | D9F591E91F05472F00769742 /* YYMemoryCache.h */,
79 | D9F591EA1F05472F00769742 /* YYMemoryCache.m */,
80 | D9F591E51F05472F00769742 /* YYDiskCache.h */,
81 | D9F591E61F05472F00769742 /* YYDiskCache.m */,
82 | D9F591E71F05472F00769742 /* YYKVStorage.h */,
83 | D9F591E81F05472F00769742 /* YYKVStorage.m */,
84 | );
85 | name = YYCache;
86 | path = ../YYCache;
87 | sourceTree = "";
88 | };
89 | D9F591F61F05477500769742 /* Frameworks */ = {
90 | isa = PBXGroup;
91 | children = (
92 | D9F591F91F05477B00769742 /* UIKit.framework */,
93 | D9F591F71F05477500769742 /* CoreFoundation.framework */,
94 | D9F591FB1F05478400769742 /* QuartzCore.framework */,
95 | D9F591F41F05474100769742 /* libsqlite3.tbd */,
96 | D93C45B01F05445A009F80F9 /* Info.plist */,
97 | );
98 | name = Frameworks;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXHeadersBuildPhase section */
104 | D93C45A91F05445A009F80F9 /* Headers */ = {
105 | isa = PBXHeadersBuildPhase;
106 | buildActionMask = 2147483647;
107 | files = (
108 | D9F591F11F05472F00769742 /* YYMemoryCache.h in Headers */,
109 | D9F591EF1F05472F00769742 /* YYKVStorage.h in Headers */,
110 | D9F591ED1F05472F00769742 /* YYDiskCache.h in Headers */,
111 | D9F591EB1F05472F00769742 /* YYCache.h in Headers */,
112 | );
113 | runOnlyForDeploymentPostprocessing = 0;
114 | };
115 | /* End PBXHeadersBuildPhase section */
116 |
117 | /* Begin PBXNativeTarget section */
118 | D93C45AB1F05445A009F80F9 /* YYCache */ = {
119 | isa = PBXNativeTarget;
120 | buildConfigurationList = D93C45B41F05445A009F80F9 /* Build configuration list for PBXNativeTarget "YYCache" */;
121 | buildPhases = (
122 | D93C45A71F05445A009F80F9 /* Sources */,
123 | D93C45A81F05445A009F80F9 /* Frameworks */,
124 | D93C45A91F05445A009F80F9 /* Headers */,
125 | D93C45AA1F05445A009F80F9 /* Resources */,
126 | );
127 | buildRules = (
128 | );
129 | dependencies = (
130 | );
131 | name = YYCache;
132 | productName = YYCache;
133 | productReference = D93C45AC1F05445A009F80F9 /* YYCache.framework */;
134 | productType = "com.apple.product-type.framework";
135 | };
136 | /* End PBXNativeTarget section */
137 |
138 | /* Begin PBXProject section */
139 | D93C45A31F05445A009F80F9 /* Project object */ = {
140 | isa = PBXProject;
141 | attributes = {
142 | LastUpgradeCheck = 0900;
143 | ORGANIZATIONNAME = ibireme;
144 | TargetAttributes = {
145 | D93C45AB1F05445A009F80F9 = {
146 | CreatedOnToolsVersion = 9.0;
147 | };
148 | };
149 | };
150 | buildConfigurationList = D93C45A61F05445A009F80F9 /* Build configuration list for PBXProject "YYCache" */;
151 | compatibilityVersion = "Xcode 8.0";
152 | developmentRegion = en;
153 | hasScannedForEncodings = 0;
154 | knownRegions = (
155 | en,
156 | );
157 | mainGroup = D93C45A21F05445A009F80F9;
158 | productRefGroup = D93C45AD1F05445A009F80F9 /* Products */;
159 | projectDirPath = "";
160 | projectRoot = "";
161 | targets = (
162 | D93C45AB1F05445A009F80F9 /* YYCache */,
163 | );
164 | };
165 | /* End PBXProject section */
166 |
167 | /* Begin PBXResourcesBuildPhase section */
168 | D93C45AA1F05445A009F80F9 /* Resources */ = {
169 | isa = PBXResourcesBuildPhase;
170 | buildActionMask = 2147483647;
171 | files = (
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | /* End PBXResourcesBuildPhase section */
176 |
177 | /* Begin PBXSourcesBuildPhase section */
178 | D93C45A71F05445A009F80F9 /* Sources */ = {
179 | isa = PBXSourcesBuildPhase;
180 | buildActionMask = 2147483647;
181 | files = (
182 | D9F591F01F05472F00769742 /* YYKVStorage.m in Sources */,
183 | D9F591F21F05472F00769742 /* YYMemoryCache.m in Sources */,
184 | D9F591EC1F05472F00769742 /* YYCache.m in Sources */,
185 | D9F591EE1F05472F00769742 /* YYDiskCache.m in Sources */,
186 | );
187 | runOnlyForDeploymentPostprocessing = 0;
188 | };
189 | /* End PBXSourcesBuildPhase section */
190 |
191 | /* Begin XCBuildConfiguration section */
192 | D93C45B21F05445A009F80F9 /* Debug */ = {
193 | isa = XCBuildConfiguration;
194 | buildSettings = {
195 | ALWAYS_SEARCH_USER_PATHS = NO;
196 | CLANG_ANALYZER_NONNULL = YES;
197 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
198 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
199 | CLANG_CXX_LIBRARY = "libc++";
200 | CLANG_ENABLE_MODULES = YES;
201 | CLANG_ENABLE_OBJC_ARC = YES;
202 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
203 | CLANG_WARN_BOOL_CONVERSION = YES;
204 | CLANG_WARN_COMMA = YES;
205 | CLANG_WARN_CONSTANT_CONVERSION = YES;
206 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
207 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
208 | CLANG_WARN_EMPTY_BODY = YES;
209 | CLANG_WARN_ENUM_CONVERSION = YES;
210 | CLANG_WARN_INFINITE_RECURSION = YES;
211 | CLANG_WARN_INT_CONVERSION = YES;
212 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
213 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
214 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
215 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
216 | CLANG_WARN_STRICT_PROTOTYPES = YES;
217 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
218 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
219 | CLANG_WARN_UNREACHABLE_CODE = YES;
220 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
221 | CODE_SIGN_IDENTITY = "iPhone Developer";
222 | COPY_PHASE_STRIP = NO;
223 | CURRENT_PROJECT_VERSION = 1;
224 | DEBUG_INFORMATION_FORMAT = dwarf;
225 | ENABLE_STRICT_OBJC_MSGSEND = YES;
226 | ENABLE_TESTABILITY = YES;
227 | GCC_C_LANGUAGE_STANDARD = gnu11;
228 | GCC_DYNAMIC_NO_PIC = NO;
229 | GCC_NO_COMMON_BLOCKS = YES;
230 | GCC_OPTIMIZATION_LEVEL = 0;
231 | GCC_PREPROCESSOR_DEFINITIONS = (
232 | "DEBUG=1",
233 | "$(inherited)",
234 | );
235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
237 | GCC_WARN_UNDECLARED_SELECTOR = YES;
238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
239 | GCC_WARN_UNUSED_FUNCTION = YES;
240 | GCC_WARN_UNUSED_VARIABLE = YES;
241 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
242 | MTL_ENABLE_DEBUG_INFO = YES;
243 | ONLY_ACTIVE_ARCH = YES;
244 | SDKROOT = iphoneos;
245 | VERSIONING_SYSTEM = "apple-generic";
246 | VERSION_INFO_PREFIX = "";
247 | };
248 | name = Debug;
249 | };
250 | D93C45B31F05445A009F80F9 /* Release */ = {
251 | isa = XCBuildConfiguration;
252 | buildSettings = {
253 | ALWAYS_SEARCH_USER_PATHS = NO;
254 | CLANG_ANALYZER_NONNULL = YES;
255 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
257 | CLANG_CXX_LIBRARY = "libc++";
258 | CLANG_ENABLE_MODULES = YES;
259 | CLANG_ENABLE_OBJC_ARC = YES;
260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
261 | CLANG_WARN_BOOL_CONVERSION = YES;
262 | CLANG_WARN_COMMA = YES;
263 | CLANG_WARN_CONSTANT_CONVERSION = YES;
264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
266 | CLANG_WARN_EMPTY_BODY = YES;
267 | CLANG_WARN_ENUM_CONVERSION = YES;
268 | CLANG_WARN_INFINITE_RECURSION = YES;
269 | CLANG_WARN_INT_CONVERSION = YES;
270 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
273 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
274 | CLANG_WARN_STRICT_PROTOTYPES = YES;
275 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
276 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
277 | CLANG_WARN_UNREACHABLE_CODE = YES;
278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
279 | CODE_SIGN_IDENTITY = "iPhone Developer";
280 | COPY_PHASE_STRIP = NO;
281 | CURRENT_PROJECT_VERSION = 1;
282 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
283 | ENABLE_NS_ASSERTIONS = NO;
284 | ENABLE_STRICT_OBJC_MSGSEND = YES;
285 | GCC_C_LANGUAGE_STANDARD = gnu11;
286 | GCC_NO_COMMON_BLOCKS = YES;
287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
289 | GCC_WARN_UNDECLARED_SELECTOR = YES;
290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
291 | GCC_WARN_UNUSED_FUNCTION = YES;
292 | GCC_WARN_UNUSED_VARIABLE = YES;
293 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
294 | MTL_ENABLE_DEBUG_INFO = NO;
295 | SDKROOT = iphoneos;
296 | VALIDATE_PRODUCT = YES;
297 | VERSIONING_SYSTEM = "apple-generic";
298 | VERSION_INFO_PREFIX = "";
299 | };
300 | name = Release;
301 | };
302 | D93C45B51F05445A009F80F9 /* Debug */ = {
303 | isa = XCBuildConfiguration;
304 | buildSettings = {
305 | APPLICATION_EXTENSION_API_ONLY = YES;
306 | CODE_SIGN_IDENTITY = "";
307 | DEFINES_MODULE = YES;
308 | DYLIB_COMPATIBILITY_VERSION = 1;
309 | DYLIB_CURRENT_VERSION = 1;
310 | DYLIB_INSTALL_NAME_BASE = "@rpath";
311 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
312 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
313 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
314 | PRODUCT_BUNDLE_IDENTIFIER = com.ibireme.YYCache;
315 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
316 | SKIP_INSTALL = YES;
317 | TARGETED_DEVICE_FAMILY = "1,2";
318 | };
319 | name = Debug;
320 | };
321 | D93C45B61F05445A009F80F9 /* Release */ = {
322 | isa = XCBuildConfiguration;
323 | buildSettings = {
324 | APPLICATION_EXTENSION_API_ONLY = YES;
325 | CODE_SIGN_IDENTITY = "";
326 | DEFINES_MODULE = YES;
327 | DYLIB_COMPATIBILITY_VERSION = 1;
328 | DYLIB_CURRENT_VERSION = 1;
329 | DYLIB_INSTALL_NAME_BASE = "@rpath";
330 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
331 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
332 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
333 | PRODUCT_BUNDLE_IDENTIFIER = com.ibireme.YYCache;
334 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
335 | SKIP_INSTALL = YES;
336 | TARGETED_DEVICE_FAMILY = "1,2";
337 | };
338 | name = Release;
339 | };
340 | /* End XCBuildConfiguration section */
341 |
342 | /* Begin XCConfigurationList section */
343 | D93C45A61F05445A009F80F9 /* Build configuration list for PBXProject "YYCache" */ = {
344 | isa = XCConfigurationList;
345 | buildConfigurations = (
346 | D93C45B21F05445A009F80F9 /* Debug */,
347 | D93C45B31F05445A009F80F9 /* Release */,
348 | );
349 | defaultConfigurationIsVisible = 0;
350 | defaultConfigurationName = Release;
351 | };
352 | D93C45B41F05445A009F80F9 /* Build configuration list for PBXNativeTarget "YYCache" */ = {
353 | isa = XCConfigurationList;
354 | buildConfigurations = (
355 | D93C45B51F05445A009F80F9 /* Debug */,
356 | D93C45B61F05445A009F80F9 /* Release */,
357 | );
358 | defaultConfigurationIsVisible = 0;
359 | defaultConfigurationName = Release;
360 | };
361 | /* End XCConfigurationList section */
362 | };
363 | rootObject = D93C45A31F05445A009F80F9 /* Project object */;
364 | }
365 |
--------------------------------------------------------------------------------
/Framework/YYCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Framework/YYCache.xcodeproj/xcshareddata/xcschemes/YYCache.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
46 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
71 |
72 |
73 |
74 |
76 |
77 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 ibireme
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | YYCache
2 | ==============
3 |
4 | [](https://raw.githubusercontent.com/ibireme/YYCache/master/LICENSE)
5 | [](https://github.com/Carthage/Carthage)
6 | [](http://cocoapods.org/pods/YYCache)
7 | [](http://cocoadocs.org/docsets/YYCache)
8 | [](https://www.apple.com/nl/ios/)
9 | [](https://travis-ci.org/ibireme/YYCache)
10 |
11 | High performance cache framework for iOS.
12 | (It's a component of [YYKit](https://github.com/ibireme/YYKit))
13 |
14 | Performance
15 | ==============
16 |
17 | 
19 |
20 | 
22 |
23 | You may [download](http://www.sqlite.org/download.html) and compile the latest version of sqlite and ignore the libsqlite3.dylib in iOS system to get higher performance.
24 |
25 | See `Benchmark/CacheBenchmark.xcodeproj` for more benchmark case.
26 |
27 |
28 | Features
29 | ==============
30 | - **LRU**: Objects can be evicted with least-recently-used algorithm.
31 | - **Limitation**: Cache limitation can be controlled with count, cost, age and free space.
32 | - **Compatibility**: The API is similar to `NSCache`, all methods are thread-safe.
33 | - **Memory Cache**
34 | - **Release Control**: Objects can be released synchronously/asynchronously on main thread or background thread.
35 | - **Automatically Clear**: It can be configured to automatically evict objects when receive memory warning or app enter background.
36 | - **Disk Cache**
37 | - **Customization**: It supports custom archive and unarchive method to store object which does not adopt NSCoding.
38 | - **Storage Type Control**: It can automatically decide the storage type (sqlite / file) for each object to get
39 | better performance.
40 |
41 |
42 | Installation
43 | ==============
44 |
45 | ### CocoaPods
46 |
47 | 1. Add `pod 'YYCache'` to your Podfile.
48 | 2. Run `pod install` or `pod update`.
49 | 3. Import \.
50 |
51 |
52 | ### Carthage
53 |
54 | 1. Add `github "ibireme/YYCache"` to your Cartfile.
55 | 2. Run `carthage update --platform ios` and add the framework to your project.
56 | 3. Import \.
57 |
58 |
59 | ### Manually
60 |
61 | 1. Download all the files in the YYCache subdirectory.
62 | 2. Add the source files to your Xcode project.
63 | 3. Link with required frameworks:
64 | * UIKit
65 | * CoreFoundation
66 | * QuartzCore
67 | * sqlite3
68 | 4. Import `YYCache.h`.
69 |
70 |
71 | Documentation
72 | ==============
73 | Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYCache/).
74 | You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc).
75 |
76 |
77 | Requirements
78 | ==============
79 | This library requires `iOS 6.0+` and `Xcode 8.0+`.
80 |
81 |
82 | License
83 | ==============
84 | YYCache is provided under the MIT license. See LICENSE file for details.
85 |
86 |
87 |
88 | ---
89 | 中文介绍
90 | ==============
91 | 高性能 iOS 缓存框架。
92 | (该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一)
93 |
94 | 性能
95 | ==============
96 |
97 | iPhone 6 上,内存缓存每秒响应次数 (越高越好):
98 | 
100 |
101 | iPhone 6 上,磁盘缓存每秒响应次数 (越高越好):
102 | 
104 |
105 | 推荐到 SQLite 官网[下载](http://www.sqlite.org/download.html)和编译最新的 SQLite,替换 iOS 自带的 libsqlite3.dylib,以获得更好的性能。
106 |
107 | 更多测试代码和用例见 `Benchmark/CacheBenchmark.xcodeproj`。
108 |
109 |
110 | 特性
111 | ==============
112 | - **LRU**: 缓存支持 LRU (least-recently-used) 淘汰算法。
113 | - **缓存控制**: 支持多种缓存控制方法:总数量、总大小、存活时间、空闲空间。
114 | - **兼容性**: API 基本和 `NSCache` 保持一致, 所有方法都是线程安全的。
115 | - **内存缓存**
116 | - **对象释放控制**: 对象的释放(release) 可以配置为同步或异步进行,可以配置在主线程或后台线程进行。
117 | - **自动清空**: 当收到内存警告或 App 进入后台时,缓存可以配置为自动清空。
118 | - **磁盘缓存**
119 | - **可定制性**: 磁盘缓存支持自定义的归档解档方法,以支持那些没有实现 NSCoding 协议的对象。
120 | - **存储类型控制**: 磁盘缓存支持对每个对象的存储类型 (SQLite/文件) 进行自动或手动控制,以获得更高的存取性能。
121 |
122 |
123 | 安装
124 | ==============
125 |
126 | ### CocoaPods
127 |
128 | 1. 在 Podfile 中添加 `pod 'YYCache'`。
129 | 2. 执行 `pod install` 或 `pod update`。
130 | 3. 导入 \。
131 |
132 |
133 | ### Carthage
134 |
135 | 1. 在 Cartfile 中添加 `github "ibireme/YYCache"`。
136 | 2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。
137 | 3. 导入 \。
138 |
139 |
140 | ### 手动安装
141 |
142 | 1. 下载 YYCache 文件夹内的所有内容。
143 | 2. 将 YYCache 内的源文件添加(拖放)到你的工程。
144 | 3. 链接以下的 frameworks:
145 | * UIKit
146 | * CoreFoundation
147 | * QuartzCore
148 | * sqlite3
149 | 4. 导入 `YYCache.h`。
150 |
151 |
152 | 文档
153 | ==============
154 | 你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYCache/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。
155 |
156 |
157 | 系统要求
158 | ==============
159 | 该项目最低支持 `iOS 6.0` 和 `Xcode 8.0`。
160 |
161 |
162 | 许可证
163 | ==============
164 | YYCache 使用 MIT 许可证,详情见 LICENSE 文件。
165 |
166 |
167 | 相关链接
168 | ==============
169 | [YYCache 设计思路与技术细节](https://blog.ibireme.com/2015/10/26/yycache/)
170 |
171 |
172 |
--------------------------------------------------------------------------------
/YYCache.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'YYCache'
3 | s.summary = 'High performance cache framework for iOS.'
4 | s.version = '1.0.4'
5 | s.license = { :type => 'MIT', :file => 'LICENSE' }
6 | s.authors = { 'ibireme' => 'ibireme@gmail.com' }
7 | s.social_media_url = 'http://blog.ibireme.com'
8 | s.homepage = 'https://github.com/ibireme/YYCache'
9 | s.platform = :ios, '6.0'
10 | s.ios.deployment_target = '6.0'
11 | s.source = { :git => 'https://github.com/ibireme/YYCache.git', :tag => s.version.to_s }
12 |
13 | s.requires_arc = true
14 | s.source_files = 'YYCache/*.{h,m}'
15 | s.public_header_files = 'YYCache/*.{h}'
16 |
17 | s.libraries = 'sqlite3'
18 | s.frameworks = 'UIKit', 'CoreFoundation', 'QuartzCore'
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/YYCache/YYCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // YYCache.h
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/2/13.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import
13 |
14 | #if __has_include()
15 | FOUNDATION_EXPORT double YYCacheVersionNumber;
16 | FOUNDATION_EXPORT const unsigned char YYCacheVersionString[];
17 | #import
18 | #import
19 | #import
20 | #elif __has_include()
21 | #import
22 | #import
23 | #import
24 | #else
25 | #import "YYMemoryCache.h"
26 | #import "YYDiskCache.h"
27 | #import "YYKVStorage.h"
28 | #endif
29 |
30 | NS_ASSUME_NONNULL_BEGIN
31 |
32 |
33 | /**
34 | `YYCache` is a thread safe key-value cache.
35 |
36 | It use `YYMemoryCache` to store objects in a small and fast memory cache,
37 | and use `YYDiskCache` to persisting objects to a large and slow disk cache.
38 | See `YYMemoryCache` and `YYDiskCache` for more information.
39 | */
40 | @interface YYCache : NSObject
41 |
42 | /** The name of the cache, readonly. */
43 | @property (copy, readonly) NSString *name;
44 |
45 | /** The underlying memory cache. see `YYMemoryCache` for more information.*/
46 | @property (strong, readonly) YYMemoryCache *memoryCache;
47 |
48 | /** The underlying disk cache. see `YYDiskCache` for more information.*/
49 | @property (strong, readonly) YYDiskCache *diskCache;
50 |
51 | /**
52 | Create a new instance with the specified name.
53 | Multiple instances with the same name will make the cache unstable.
54 |
55 | @param name The name of the cache. It will create a dictionary with the name in
56 | the app's caches dictionary for disk cache. Once initialized you should not
57 | read and write to this directory.
58 | @result A new cache object, or nil if an error occurs.
59 | */
60 | - (nullable instancetype)initWithName:(NSString *)name;
61 |
62 | /**
63 | Create a new instance with the specified path.
64 | Multiple instances with the same name will make the cache unstable.
65 |
66 | @param path Full path of a directory in which the cache will write data.
67 | Once initialized you should not read and write to this directory.
68 | @result A new cache object, or nil if an error occurs.
69 | */
70 | - (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
71 |
72 | /**
73 | Convenience Initializers
74 | Create a new instance with the specified name.
75 | Multiple instances with the same name will make the cache unstable.
76 |
77 | @param name The name of the cache. It will create a dictionary with the name in
78 | the app's caches dictionary for disk cache. Once initialized you should not
79 | read and write to this directory.
80 | @result A new cache object, or nil if an error occurs.
81 | */
82 | + (nullable instancetype)cacheWithName:(NSString *)name;
83 |
84 | /**
85 | Convenience Initializers
86 | Create a new instance with the specified path.
87 | Multiple instances with the same name will make the cache unstable.
88 |
89 | @param path Full path of a directory in which the cache will write data.
90 | Once initialized you should not read and write to this directory.
91 | @result A new cache object, or nil if an error occurs.
92 | */
93 | + (nullable instancetype)cacheWithPath:(NSString *)path;
94 |
95 | - (instancetype)init UNAVAILABLE_ATTRIBUTE;
96 | + (instancetype)new UNAVAILABLE_ATTRIBUTE;
97 |
98 | #pragma mark - Access Methods
99 | ///=============================================================================
100 | /// @name Access Methods
101 | ///=============================================================================
102 |
103 | /**
104 | Returns a boolean value that indicates whether a given key is in cache.
105 | This method may blocks the calling thread until file read finished.
106 |
107 | @param key A string identifying the value. If nil, just return NO.
108 | @return Whether the key is in cache.
109 | */
110 | - (BOOL)containsObjectForKey:(NSString *)key;
111 |
112 | /**
113 | Returns a boolean value with the block that indicates whether a given key is in cache.
114 | This method returns immediately and invoke the passed block in background queue
115 | when the operation finished.
116 |
117 | @param key A string identifying the value. If nil, just return NO.
118 | @param block A block which will be invoked in background queue when finished.
119 | */
120 | - (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;
121 |
122 | /**
123 | Returns the value associated with a given key.
124 | This method may blocks the calling thread until file read finished.
125 |
126 | @param key A string identifying the value. If nil, just return nil.
127 | @return The value associated with key, or nil if no value is associated with key.
128 | */
129 | - (nullable id)objectForKey:(NSString *)key;
130 |
131 | /**
132 | Returns the value associated with a given key.
133 | This method returns immediately and invoke the passed block in background queue
134 | when the operation finished.
135 |
136 | @param key A string identifying the value. If nil, just return nil.
137 | @param block A block which will be invoked in background queue when finished.
138 | */
139 | - (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id object))block;
140 |
141 | /**
142 | Sets the value of the specified key in the cache.
143 | This method may blocks the calling thread until file write finished.
144 |
145 | @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
146 | @param key The key with which to associate the value. If nil, this method has no effect.
147 | */
148 | - (void)setObject:(nullable id)object forKey:(NSString *)key;
149 |
150 | /**
151 | Sets the value of the specified key in the cache.
152 | This method returns immediately and invoke the passed block in background queue
153 | when the operation finished.
154 |
155 | @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
156 | @param block A block which will be invoked in background queue when finished.
157 | */
158 | - (void)setObject:(nullable id)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;
159 |
160 | /**
161 | Removes the value of the specified key in the cache.
162 | This method may blocks the calling thread until file delete finished.
163 |
164 | @param key The key identifying the value to be removed. If nil, this method has no effect.
165 | */
166 | - (void)removeObjectForKey:(NSString *)key;
167 |
168 | /**
169 | Removes the value of the specified key in the cache.
170 | This method returns immediately and invoke the passed block in background queue
171 | when the operation finished.
172 |
173 | @param key The key identifying the value to be removed. If nil, this method has no effect.
174 | @param block A block which will be invoked in background queue when finished.
175 | */
176 | - (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;
177 |
178 | /**
179 | Empties the cache.
180 | This method may blocks the calling thread until file delete finished.
181 | */
182 | - (void)removeAllObjects;
183 |
184 | /**
185 | Empties the cache.
186 | This method returns immediately and invoke the passed block in background queue
187 | when the operation finished.
188 |
189 | @param block A block which will be invoked in background queue when finished.
190 | */
191 | - (void)removeAllObjectsWithBlock:(void(^)(void))block;
192 |
193 | /**
194 | Empties the cache with block.
195 | This method returns immediately and executes the clear operation with block in background.
196 |
197 | @warning You should not send message to this instance in these blocks.
198 | @param progress This block will be invoked during removing, pass nil to ignore.
199 | @param end This block will be invoked at the end, pass nil to ignore.
200 | */
201 | - (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
202 | endBlock:(nullable void(^)(BOOL error))end;
203 |
204 | @end
205 |
206 | NS_ASSUME_NONNULL_END
207 |
--------------------------------------------------------------------------------
/YYCache/YYCache.m:
--------------------------------------------------------------------------------
1 | //
2 | // YYCache.m
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/2/13.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import "YYCache.h"
13 | #import "YYMemoryCache.h"
14 | #import "YYDiskCache.h"
15 |
16 | @implementation YYCache
17 |
18 | - (instancetype) init {
19 | NSLog(@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance.");
20 | return [self initWithPath:@""];
21 | }
22 |
23 | - (instancetype)initWithName:(NSString *)name {
24 | if (name.length == 0) return nil;
25 | NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
26 | NSString *path = [cacheFolder stringByAppendingPathComponent:name];
27 | return [self initWithPath:path];
28 | }
29 |
30 | - (instancetype)initWithPath:(NSString *)path {
31 | if (path.length == 0) return nil;
32 | YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
33 | if (!diskCache) return nil;
34 | NSString *name = [path lastPathComponent];
35 | YYMemoryCache *memoryCache = [YYMemoryCache new];
36 | memoryCache.name = name;
37 |
38 | self = [super init];
39 | _name = name;
40 | _diskCache = diskCache;
41 | _memoryCache = memoryCache;
42 | return self;
43 | }
44 |
45 | + (instancetype)cacheWithName:(NSString *)name {
46 | return [[self alloc] initWithName:name];
47 | }
48 |
49 | + (instancetype)cacheWithPath:(NSString *)path {
50 | return [[self alloc] initWithPath:path];
51 | }
52 |
53 | - (BOOL)containsObjectForKey:(NSString *)key {
54 | return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
55 | }
56 |
57 | - (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block {
58 | if (!block) return;
59 |
60 | if ([_memoryCache containsObjectForKey:key]) {
61 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
62 | block(key, YES);
63 | });
64 | } else {
65 | [_diskCache containsObjectForKey:key withBlock:block];
66 | }
67 | }
68 |
69 | - (id)objectForKey:(NSString *)key {
70 | id object = [_memoryCache objectForKey:key];
71 | if (!object) {
72 | object = [_diskCache objectForKey:key];
73 | if (object) {
74 | [_memoryCache setObject:object forKey:key];
75 | }
76 | }
77 | return object;
78 | }
79 |
80 | - (void)objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id object))block {
81 | if (!block) return;
82 | id object = [_memoryCache objectForKey:key];
83 | if (object) {
84 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
85 | block(key, object);
86 | });
87 | } else {
88 | [_diskCache objectForKey:key withBlock:^(NSString *key, id object) {
89 | if (object && ![_memoryCache objectForKey:key]) {
90 | [_memoryCache setObject:object forKey:key];
91 | }
92 | block(key, object);
93 | }];
94 | }
95 | }
96 |
97 | - (void)setObject:(id)object forKey:(NSString *)key {
98 | [_memoryCache setObject:object forKey:key];
99 | [_diskCache setObject:object forKey:key];
100 | }
101 |
102 | - (void)setObject:(id)object forKey:(NSString *)key withBlock:(void (^)(void))block {
103 | [_memoryCache setObject:object forKey:key];
104 | [_diskCache setObject:object forKey:key withBlock:block];
105 | }
106 |
107 | - (void)removeObjectForKey:(NSString *)key {
108 | [_memoryCache removeObjectForKey:key];
109 | [_diskCache removeObjectForKey:key];
110 | }
111 |
112 | - (void)removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block {
113 | [_memoryCache removeObjectForKey:key];
114 | [_diskCache removeObjectForKey:key withBlock:block];
115 | }
116 |
117 | - (void)removeAllObjects {
118 | [_memoryCache removeAllObjects];
119 | [_diskCache removeAllObjects];
120 | }
121 |
122 | - (void)removeAllObjectsWithBlock:(void(^)(void))block {
123 | [_memoryCache removeAllObjects];
124 | [_diskCache removeAllObjectsWithBlock:block];
125 | }
126 |
127 | - (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
128 | endBlock:(void(^)(BOOL error))end {
129 | [_memoryCache removeAllObjects];
130 | [_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end];
131 |
132 | }
133 |
134 | - (NSString *)description {
135 | if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
136 | else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
137 | }
138 |
139 | @end
140 |
--------------------------------------------------------------------------------
/YYCache/YYDiskCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // YYDiskCache.h
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/2/11.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | /**
17 | YYDiskCache is a thread-safe cache that stores key-value pairs backed by SQLite
18 | and file system (similar to NSURLCache's disk cache).
19 |
20 | YYDiskCache has these features:
21 |
22 | * It use LRU (least-recently-used) to remove objects.
23 | * It can be controlled by cost, count, and age.
24 | * It can be configured to automatically evict objects when there's no free disk space.
25 | * It can automatically decide the storage type (sqlite/file) for each object to get
26 | better performance.
27 |
28 | You may compile the latest version of sqlite and ignore the libsqlite3.dylib in
29 | iOS system to get 2x~4x speed up.
30 | */
31 | @interface YYDiskCache : NSObject
32 |
33 | #pragma mark - Attribute
34 | ///=============================================================================
35 | /// @name Attribute
36 | ///=============================================================================
37 |
38 | /** The name of the cache. Default is nil. */
39 | @property (nullable, copy) NSString *name;
40 |
41 | /** The path of the cache (read-only). */
42 | @property (readonly) NSString *path;
43 |
44 | /**
45 | If the object's data size (in bytes) is larger than this value, then object will
46 | be stored as a file, otherwise the object will be stored in sqlite.
47 |
48 | 0 means all objects will be stored as separated files, NSUIntegerMax means all
49 | objects will be stored in sqlite.
50 |
51 | The default value is 20480 (20KB).
52 | */
53 | @property (readonly) NSUInteger inlineThreshold;
54 |
55 | /**
56 | If this block is not nil, then the block will be used to archive object instead
57 | of NSKeyedArchiver. You can use this block to support the objects which do not
58 | conform to the `NSCoding` protocol.
59 |
60 | The default value is nil.
61 | */
62 | @property (nullable, copy) NSData *(^customArchiveBlock)(id object);
63 |
64 | /**
65 | If this block is not nil, then the block will be used to unarchive object instead
66 | of NSKeyedUnarchiver. You can use this block to support the objects which do not
67 | conform to the `NSCoding` protocol.
68 |
69 | The default value is nil.
70 | */
71 | @property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);
72 |
73 | /**
74 | When an object needs to be saved as a file, this block will be invoked to generate
75 | a file name for a specified key. If the block is nil, the cache use md5(key) as
76 | default file name.
77 |
78 | The default value is nil.
79 | */
80 | @property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key);
81 |
82 |
83 |
84 | #pragma mark - Limit
85 | ///=============================================================================
86 | /// @name Limit
87 | ///=============================================================================
88 |
89 | /**
90 | The maximum number of objects the cache should hold.
91 |
92 | @discussion The default value is NSUIntegerMax, which means no limit.
93 | This is not a strict limit — if the cache goes over the limit, some objects in the
94 | cache could be evicted later in background queue.
95 | */
96 | @property NSUInteger countLimit;
97 |
98 | /**
99 | The maximum total cost that the cache can hold before it starts evicting objects.
100 |
101 | @discussion The default value is NSUIntegerMax, which means no limit.
102 | This is not a strict limit — if the cache goes over the limit, some objects in the
103 | cache could be evicted later in background queue.
104 | */
105 | @property NSUInteger costLimit;
106 |
107 | /**
108 | The maximum expiry time of objects in cache.
109 |
110 | @discussion The default value is DBL_MAX, which means no limit.
111 | This is not a strict limit — if an object goes over the limit, the objects could
112 | be evicted later in background queue.
113 | */
114 | @property NSTimeInterval ageLimit;
115 |
116 | /**
117 | The minimum free disk space (in bytes) which the cache should kept.
118 |
119 | @discussion The default value is 0, which means no limit.
120 | If the free disk space is lower than this value, the cache will remove objects
121 | to free some disk space. This is not a strict limit—if the free disk space goes
122 | over the limit, the objects could be evicted later in background queue.
123 | */
124 | @property NSUInteger freeDiskSpaceLimit;
125 |
126 | /**
127 | The auto trim check time interval in seconds. Default is 60 (1 minute).
128 |
129 | @discussion The cache holds an internal timer to check whether the cache reaches
130 | its limits, and if the limit is reached, it begins to evict objects.
131 | */
132 | @property NSTimeInterval autoTrimInterval;
133 |
134 | /**
135 | Set `YES` to enable error logs for debug.
136 | */
137 | @property BOOL errorLogsEnabled;
138 |
139 | #pragma mark - Initializer
140 | ///=============================================================================
141 | /// @name Initializer
142 | ///=============================================================================
143 | - (instancetype)init UNAVAILABLE_ATTRIBUTE;
144 | + (instancetype)new UNAVAILABLE_ATTRIBUTE;
145 |
146 | /**
147 | Create a new cache based on the specified path.
148 |
149 | @param path Full path of a directory in which the cache will write data.
150 | Once initialized you should not read and write to this directory.
151 |
152 | @return A new cache object, or nil if an error occurs.
153 |
154 | @warning If the cache instance for the specified path already exists in memory,
155 | this method will return it directly, instead of creating a new instance.
156 | */
157 | - (nullable instancetype)initWithPath:(NSString *)path;
158 |
159 | /**
160 | The designated initializer.
161 |
162 | @param path Full path of a directory in which the cache will write data.
163 | Once initialized you should not read and write to this directory.
164 |
165 | @param threshold The data store inline threshold in bytes. If the object's data
166 | size (in bytes) is larger than this value, then object will be stored as a
167 | file, otherwise the object will be stored in sqlite. 0 means all objects will
168 | be stored as separated files, NSUIntegerMax means all objects will be stored
169 | in sqlite. If you don't know your object's size, 20480 is a good choice.
170 | After first initialized you should not change this value of the specified path.
171 |
172 | @return A new cache object, or nil if an error occurs.
173 |
174 | @warning If the cache instance for the specified path already exists in memory,
175 | this method will return it directly, instead of creating a new instance.
176 | */
177 | - (nullable instancetype)initWithPath:(NSString *)path
178 | inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;
179 |
180 |
181 | #pragma mark - Access Methods
182 | ///=============================================================================
183 | /// @name Access Methods
184 | ///=============================================================================
185 |
186 | /**
187 | Returns a boolean value that indicates whether a given key is in cache.
188 | This method may blocks the calling thread until file read finished.
189 |
190 | @param key A string identifying the value. If nil, just return NO.
191 | @return Whether the key is in cache.
192 | */
193 | - (BOOL)containsObjectForKey:(NSString *)key;
194 |
195 | /**
196 | Returns a boolean value with the block that indicates whether a given key is in cache.
197 | This method returns immediately and invoke the passed block in background queue
198 | when the operation finished.
199 |
200 | @param key A string identifying the value. If nil, just return NO.
201 | @param block A block which will be invoked in background queue when finished.
202 | */
203 | - (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block;
204 |
205 | /**
206 | Returns the value associated with a given key.
207 | This method may blocks the calling thread until file read finished.
208 |
209 | @param key A string identifying the value. If nil, just return nil.
210 | @return The value associated with key, or nil if no value is associated with key.
211 | */
212 | - (nullable id)objectForKey:(NSString *)key;
213 |
214 | /**
215 | Returns the value associated with a given key.
216 | This method returns immediately and invoke the passed block in background queue
217 | when the operation finished.
218 |
219 | @param key A string identifying the value. If nil, just return nil.
220 | @param block A block which will be invoked in background queue when finished.
221 | */
222 | - (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id _Nullable object))block;
223 |
224 | /**
225 | Sets the value of the specified key in the cache.
226 | This method may blocks the calling thread until file write finished.
227 |
228 | @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
229 | @param key The key with which to associate the value. If nil, this method has no effect.
230 | */
231 | - (void)setObject:(nullable id)object forKey:(NSString *)key;
232 |
233 | /**
234 | Sets the value of the specified key in the cache.
235 | This method returns immediately and invoke the passed block in background queue
236 | when the operation finished.
237 |
238 | @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
239 | @param block A block which will be invoked in background queue when finished.
240 | */
241 | - (void)setObject:(nullable id)object forKey:(NSString *)key withBlock:(void(^)(void))block;
242 |
243 | /**
244 | Removes the value of the specified key in the cache.
245 | This method may blocks the calling thread until file delete finished.
246 |
247 | @param key The key identifying the value to be removed. If nil, this method has no effect.
248 | */
249 | - (void)removeObjectForKey:(NSString *)key;
250 |
251 | /**
252 | Removes the value of the specified key in the cache.
253 | This method returns immediately and invoke the passed block in background queue
254 | when the operation finished.
255 |
256 | @param key The key identifying the value to be removed. If nil, this method has no effect.
257 | @param block A block which will be invoked in background queue when finished.
258 | */
259 | - (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block;
260 |
261 | /**
262 | Empties the cache.
263 | This method may blocks the calling thread until file delete finished.
264 | */
265 | - (void)removeAllObjects;
266 |
267 | /**
268 | Empties the cache.
269 | This method returns immediately and invoke the passed block in background queue
270 | when the operation finished.
271 |
272 | @param block A block which will be invoked in background queue when finished.
273 | */
274 | - (void)removeAllObjectsWithBlock:(void(^)(void))block;
275 |
276 | /**
277 | Empties the cache with block.
278 | This method returns immediately and executes the clear operation with block in background.
279 |
280 | @warning You should not send message to this instance in these blocks.
281 | @param progress This block will be invoked during removing, pass nil to ignore.
282 | @param end This block will be invoked at the end, pass nil to ignore.
283 | */
284 | - (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
285 | endBlock:(nullable void(^)(BOOL error))end;
286 |
287 |
288 | /**
289 | Returns the number of objects in this cache.
290 | This method may blocks the calling thread until file read finished.
291 |
292 | @return The total objects count.
293 | */
294 | - (NSInteger)totalCount;
295 |
296 | /**
297 | Get the number of objects in this cache.
298 | This method returns immediately and invoke the passed block in background queue
299 | when the operation finished.
300 |
301 | @param block A block which will be invoked in background queue when finished.
302 | */
303 | - (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;
304 |
305 | /**
306 | Returns the total cost (in bytes) of objects in this cache.
307 | This method may blocks the calling thread until file read finished.
308 |
309 | @return The total objects cost in bytes.
310 | */
311 | - (NSInteger)totalCost;
312 |
313 | /**
314 | Get the total cost (in bytes) of objects in this cache.
315 | This method returns immediately and invoke the passed block in background queue
316 | when the operation finished.
317 |
318 | @param block A block which will be invoked in background queue when finished.
319 | */
320 | - (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block;
321 |
322 |
323 | #pragma mark - Trim
324 | ///=============================================================================
325 | /// @name Trim
326 | ///=============================================================================
327 |
328 | /**
329 | Removes objects from the cache use LRU, until the `totalCount` is below the specified value.
330 | This method may blocks the calling thread until operation finished.
331 |
332 | @param count The total count allowed to remain after the cache has been trimmed.
333 | */
334 | - (void)trimToCount:(NSUInteger)count;
335 |
336 | /**
337 | Removes objects from the cache use LRU, until the `totalCount` is below the specified value.
338 | This method returns immediately and invoke the passed block in background queue
339 | when the operation finished.
340 |
341 | @param count The total count allowed to remain after the cache has been trimmed.
342 | @param block A block which will be invoked in background queue when finished.
343 | */
344 | - (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
345 |
346 | /**
347 | Removes objects from the cache use LRU, until the `totalCost` is below the specified value.
348 | This method may blocks the calling thread until operation finished.
349 |
350 | @param cost The total cost allowed to remain after the cache has been trimmed.
351 | */
352 | - (void)trimToCost:(NSUInteger)cost;
353 |
354 | /**
355 | Removes objects from the cache use LRU, until the `totalCost` is below the specified value.
356 | This method returns immediately and invoke the passed block in background queue
357 | when the operation finished.
358 |
359 | @param cost The total cost allowed to remain after the cache has been trimmed.
360 | @param block A block which will be invoked in background queue when finished.
361 | */
362 | - (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block;
363 |
364 | /**
365 | Removes objects from the cache use LRU, until all expiry objects removed by the specified value.
366 | This method may blocks the calling thread until operation finished.
367 |
368 | @param age The maximum age of the object.
369 | */
370 | - (void)trimToAge:(NSTimeInterval)age;
371 |
372 | /**
373 | Removes objects from the cache use LRU, until all expiry objects removed by the specified value.
374 | This method returns immediately and invoke the passed block in background queue
375 | when the operation finished.
376 |
377 | @param age The maximum age of the object.
378 | @param block A block which will be invoked in background queue when finished.
379 | */
380 | - (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block;
381 |
382 |
383 | #pragma mark - Extended Data
384 | ///=============================================================================
385 | /// @name Extended Data
386 | ///=============================================================================
387 |
388 | /**
389 | Get extended data from an object.
390 |
391 | @discussion See 'setExtendedData:toObject:' for more information.
392 |
393 | @param object An object.
394 | @return The extended data.
395 | */
396 | + (nullable NSData *)getExtendedDataFromObject:(id)object;
397 |
398 | /**
399 | Set extended data to an object.
400 |
401 | @discussion You can set any extended data to an object before you save the object
402 | to disk cache. The extended data will also be saved with this object. You can get
403 | the extended data later with "getExtendedDataFromObject:".
404 |
405 | @param extendedData The extended data (pass nil to remove).
406 | @param object The object.
407 | */
408 | + (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
409 |
410 | @end
411 |
412 | NS_ASSUME_NONNULL_END
413 |
--------------------------------------------------------------------------------
/YYCache/YYDiskCache.m:
--------------------------------------------------------------------------------
1 | //
2 | // YYDiskCache.m
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/2/11.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import "YYDiskCache.h"
13 | #import "YYKVStorage.h"
14 | #import
15 | #import
16 | #import
17 | #import
18 |
19 | #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
20 | #define Unlock() dispatch_semaphore_signal(self->_lock)
21 |
22 | static const int extended_data_key;
23 |
24 | /// Free disk space in bytes.
25 | static int64_t _YYDiskSpaceFree() {
26 | NSError *error = nil;
27 | NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];
28 | if (error) return -1;
29 | int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue];
30 | if (space < 0) space = -1;
31 | return space;
32 | }
33 |
34 | /// String's md5 hash.
35 | static NSString *_YYNSStringMD5(NSString *string) {
36 | if (!string) return nil;
37 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
38 | unsigned char result[CC_MD5_DIGEST_LENGTH];
39 | CC_MD5(data.bytes, (CC_LONG)data.length, result);
40 | return [NSString stringWithFormat:
41 | @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
42 | result[0], result[1], result[2], result[3],
43 | result[4], result[5], result[6], result[7],
44 | result[8], result[9], result[10], result[11],
45 | result[12], result[13], result[14], result[15]
46 | ];
47 | }
48 |
49 | /// weak reference for all instances
50 | static NSMapTable *_globalInstances;
51 | static dispatch_semaphore_t _globalInstancesLock;
52 |
53 | static void _YYDiskCacheInitGlobal() {
54 | static dispatch_once_t onceToken;
55 | dispatch_once(&onceToken, ^{
56 | _globalInstancesLock = dispatch_semaphore_create(1);
57 | _globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
58 | });
59 | }
60 |
61 | static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
62 | if (path.length == 0) return nil;
63 | _YYDiskCacheInitGlobal();
64 | dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
65 | id cache = [_globalInstances objectForKey:path];
66 | dispatch_semaphore_signal(_globalInstancesLock);
67 | return cache;
68 | }
69 |
70 | static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
71 | if (cache.path.length == 0) return;
72 | _YYDiskCacheInitGlobal();
73 | dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
74 | [_globalInstances setObject:cache forKey:cache.path];
75 | dispatch_semaphore_signal(_globalInstancesLock);
76 | }
77 |
78 |
79 |
80 | @implementation YYDiskCache {
81 | YYKVStorage *_kv;
82 | dispatch_semaphore_t _lock;
83 | dispatch_queue_t _queue;
84 | }
85 |
86 | - (void)_trimRecursively {
87 | __weak typeof(self) _self = self;
88 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
89 | __strong typeof(_self) self = _self;
90 | if (!self) return;
91 | [self _trimInBackground];
92 | [self _trimRecursively];
93 | });
94 | }
95 |
96 | - (void)_trimInBackground {
97 | __weak typeof(self) _self = self;
98 | dispatch_async(_queue, ^{
99 | __strong typeof(_self) self = _self;
100 | if (!self) return;
101 | Lock();
102 | [self _trimToCost:self.costLimit];
103 | [self _trimToCount:self.countLimit];
104 | [self _trimToAge:self.ageLimit];
105 | [self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
106 | Unlock();
107 | });
108 | }
109 |
110 | - (void)_trimToCost:(NSUInteger)costLimit {
111 | if (costLimit >= INT_MAX) return;
112 | [_kv removeItemsToFitSize:(int)costLimit];
113 |
114 | }
115 |
116 | - (void)_trimToCount:(NSUInteger)countLimit {
117 | if (countLimit >= INT_MAX) return;
118 | [_kv removeItemsToFitCount:(int)countLimit];
119 | }
120 |
121 | - (void)_trimToAge:(NSTimeInterval)ageLimit {
122 | if (ageLimit <= 0) {
123 | [_kv removeAllItems];
124 | return;
125 | }
126 | long timestamp = time(NULL);
127 | if (timestamp <= ageLimit) return;
128 | long age = timestamp - ageLimit;
129 | if (age >= INT_MAX) return;
130 | [_kv removeItemsEarlierThanTime:(int)age];
131 | }
132 |
133 | - (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace {
134 | if (targetFreeDiskSpace == 0) return;
135 | int64_t totalBytes = [_kv getItemsSize];
136 | if (totalBytes <= 0) return;
137 | int64_t diskFreeBytes = _YYDiskSpaceFree();
138 | if (diskFreeBytes < 0) return;
139 | int64_t needTrimBytes = targetFreeDiskSpace - diskFreeBytes;
140 | if (needTrimBytes <= 0) return;
141 | int64_t costLimit = totalBytes - needTrimBytes;
142 | if (costLimit < 0) costLimit = 0;
143 | [self _trimToCost:(int)costLimit];
144 | }
145 |
146 | - (NSString *)_filenameForKey:(NSString *)key {
147 | NSString *filename = nil;
148 | if (_customFileNameBlock) filename = _customFileNameBlock(key);
149 | if (!filename) filename = _YYNSStringMD5(key);
150 | return filename;
151 | }
152 |
153 | - (void)_appWillBeTerminated {
154 | Lock();
155 | _kv = nil;
156 | Unlock();
157 | }
158 |
159 | #pragma mark - public
160 |
161 | - (void)dealloc {
162 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
163 | }
164 |
165 | - (instancetype)init {
166 | @throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil];
167 | return [self initWithPath:@"" inlineThreshold:0];
168 | }
169 |
170 | - (instancetype)initWithPath:(NSString *)path {
171 | return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
172 | }
173 |
174 | - (instancetype)initWithPath:(NSString *)path
175 | inlineThreshold:(NSUInteger)threshold {
176 | self = [super init];
177 | if (!self) return nil;
178 |
179 | YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
180 | if (globalCache) return globalCache;
181 |
182 | YYKVStorageType type;
183 | if (threshold == 0) {
184 | type = YYKVStorageTypeFile;
185 | } else if (threshold == NSUIntegerMax) {
186 | type = YYKVStorageTypeSQLite;
187 | } else {
188 | type = YYKVStorageTypeMixed;
189 | }
190 |
191 | YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
192 | if (!kv) return nil;
193 |
194 | _kv = kv;
195 | _path = path;
196 | _lock = dispatch_semaphore_create(1);
197 | _queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT);
198 | _inlineThreshold = threshold;
199 | _countLimit = NSUIntegerMax;
200 | _costLimit = NSUIntegerMax;
201 | _ageLimit = DBL_MAX;
202 | _freeDiskSpaceLimit = 0;
203 | _autoTrimInterval = 60;
204 |
205 | [self _trimRecursively];
206 | _YYDiskCacheSetGlobal(self);
207 |
208 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
209 | return self;
210 | }
211 |
212 | - (BOOL)containsObjectForKey:(NSString *)key {
213 | if (!key) return NO;
214 | Lock();
215 | BOOL contains = [_kv itemExistsForKey:key];
216 | Unlock();
217 | return contains;
218 | }
219 |
220 | - (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block {
221 | if (!block) return;
222 | __weak typeof(self) _self = self;
223 | dispatch_async(_queue, ^{
224 | __strong typeof(_self) self = _self;
225 | BOOL contains = [self containsObjectForKey:key];
226 | block(key, contains);
227 | });
228 | }
229 |
230 | - (id)objectForKey:(NSString *)key {
231 | if (!key) return nil;
232 | Lock();
233 | YYKVStorageItem *item = [_kv getItemForKey:key];
234 | Unlock();
235 | if (!item.value) return nil;
236 |
237 | id object = nil;
238 | if (_customUnarchiveBlock) {
239 | object = _customUnarchiveBlock(item.value);
240 | } else {
241 | @try {
242 | object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
243 | }
244 | @catch (NSException *exception) {
245 | // nothing to do...
246 | }
247 | }
248 | if (object && item.extendedData) {
249 | [YYDiskCache setExtendedData:item.extendedData toObject:object];
250 | }
251 | return object;
252 | }
253 |
254 | - (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id object))block {
255 | if (!block) return;
256 | __weak typeof(self) _self = self;
257 | dispatch_async(_queue, ^{
258 | __strong typeof(_self) self = _self;
259 | id object = [self objectForKey:key];
260 | block(key, object);
261 | });
262 | }
263 |
264 | - (void)setObject:(id)object forKey:(NSString *)key {
265 | if (!key) return;
266 | if (!object) {
267 | [self removeObjectForKey:key];
268 | return;
269 | }
270 |
271 | NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
272 | NSData *value = nil;
273 | if (_customArchiveBlock) {
274 | value = _customArchiveBlock(object);
275 | } else {
276 | @try {
277 | value = [NSKeyedArchiver archivedDataWithRootObject:object];
278 | }
279 | @catch (NSException *exception) {
280 | // nothing to do...
281 | }
282 | }
283 | if (!value) return;
284 | NSString *filename = nil;
285 | if (_kv.type != YYKVStorageTypeSQLite) {
286 | if (value.length > _inlineThreshold) {
287 | filename = [self _filenameForKey:key];
288 | }
289 | }
290 |
291 | Lock();
292 | [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
293 | Unlock();
294 | }
295 |
296 | - (void)setObject:(id)object forKey:(NSString *)key withBlock:(void(^)(void))block {
297 | __weak typeof(self) _self = self;
298 | dispatch_async(_queue, ^{
299 | __strong typeof(_self) self = _self;
300 | [self setObject:object forKey:key];
301 | if (block) block();
302 | });
303 | }
304 |
305 | - (void)removeObjectForKey:(NSString *)key {
306 | if (!key) return;
307 | Lock();
308 | [_kv removeItemForKey:key];
309 | Unlock();
310 | }
311 |
312 | - (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block {
313 | __weak typeof(self) _self = self;
314 | dispatch_async(_queue, ^{
315 | __strong typeof(_self) self = _self;
316 | [self removeObjectForKey:key];
317 | if (block) block(key);
318 | });
319 | }
320 |
321 | - (void)removeAllObjects {
322 | Lock();
323 | [_kv removeAllItems];
324 | Unlock();
325 | }
326 |
327 | - (void)removeAllObjectsWithBlock:(void(^)(void))block {
328 | __weak typeof(self) _self = self;
329 | dispatch_async(_queue, ^{
330 | __strong typeof(_self) self = _self;
331 | [self removeAllObjects];
332 | if (block) block();
333 | });
334 | }
335 |
336 | - (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
337 | endBlock:(void(^)(BOOL error))end {
338 | __weak typeof(self) _self = self;
339 | dispatch_async(_queue, ^{
340 | __strong typeof(_self) self = _self;
341 | if (!self) {
342 | if (end) end(YES);
343 | return;
344 | }
345 | Lock();
346 | [_kv removeAllItemsWithProgressBlock:progress endBlock:end];
347 | Unlock();
348 | });
349 | }
350 |
351 | - (NSInteger)totalCount {
352 | Lock();
353 | int count = [_kv getItemsCount];
354 | Unlock();
355 | return count;
356 | }
357 |
358 | - (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block {
359 | if (!block) return;
360 | __weak typeof(self) _self = self;
361 | dispatch_async(_queue, ^{
362 | __strong typeof(_self) self = _self;
363 | NSInteger totalCount = [self totalCount];
364 | block(totalCount);
365 | });
366 | }
367 |
368 | - (NSInteger)totalCost {
369 | Lock();
370 | int count = [_kv getItemsSize];
371 | Unlock();
372 | return count;
373 | }
374 |
375 | - (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block {
376 | if (!block) return;
377 | __weak typeof(self) _self = self;
378 | dispatch_async(_queue, ^{
379 | __strong typeof(_self) self = _self;
380 | NSInteger totalCost = [self totalCost];
381 | block(totalCost);
382 | });
383 | }
384 |
385 | - (void)trimToCount:(NSUInteger)count {
386 | Lock();
387 | [self _trimToCount:count];
388 | Unlock();
389 | }
390 |
391 | - (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block {
392 | __weak typeof(self) _self = self;
393 | dispatch_async(_queue, ^{
394 | __strong typeof(_self) self = _self;
395 | [self trimToCount:count];
396 | if (block) block();
397 | });
398 | }
399 |
400 | - (void)trimToCost:(NSUInteger)cost {
401 | Lock();
402 | [self _trimToCost:cost];
403 | Unlock();
404 | }
405 |
406 | - (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block {
407 | __weak typeof(self) _self = self;
408 | dispatch_async(_queue, ^{
409 | __strong typeof(_self) self = _self;
410 | [self trimToCost:cost];
411 | if (block) block();
412 | });
413 | }
414 |
415 | - (void)trimToAge:(NSTimeInterval)age {
416 | Lock();
417 | [self _trimToAge:age];
418 | Unlock();
419 | }
420 |
421 | - (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block {
422 | __weak typeof(self) _self = self;
423 | dispatch_async(_queue, ^{
424 | __strong typeof(_self) self = _self;
425 | [self trimToAge:age];
426 | if (block) block();
427 | });
428 | }
429 |
430 | + (NSData *)getExtendedDataFromObject:(id)object {
431 | if (!object) return nil;
432 | return (NSData *)objc_getAssociatedObject(object, &extended_data_key);
433 | }
434 |
435 | + (void)setExtendedData:(NSData *)extendedData toObject:(id)object {
436 | if (!object) return;
437 | objc_setAssociatedObject(object, &extended_data_key, extendedData, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
438 | }
439 |
440 | - (NSString *)description {
441 | if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@:%@)", self.class, self, _name, _path];
442 | else return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _path];
443 | }
444 |
445 | - (BOOL)errorLogsEnabled {
446 | Lock();
447 | BOOL enabled = _kv.errorLogsEnabled;
448 | Unlock();
449 | return enabled;
450 | }
451 |
452 | - (void)setErrorLogsEnabled:(BOOL)errorLogsEnabled {
453 | Lock();
454 | _kv.errorLogsEnabled = errorLogsEnabled;
455 | Unlock();
456 | }
457 |
458 | @end
459 |
--------------------------------------------------------------------------------
/YYCache/YYKVStorage.h:
--------------------------------------------------------------------------------
1 | //
2 | // YYKVStorage.h
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/4/22.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | /**
17 | YYKVStorageItem is used by `YYKVStorage` to store key-value pair and meta data.
18 | Typically, you should not use this class directly.
19 | */
20 | @interface YYKVStorageItem : NSObject
21 | @property (nonatomic, strong) NSString *key; ///< key
22 | @property (nonatomic, strong) NSData *value; ///< value
23 | @property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline)
24 | @property (nonatomic) int size; ///< value's size in bytes
25 | @property (nonatomic) int modTime; ///< modification unix timestamp
26 | @property (nonatomic) int accessTime; ///< last access unix timestamp
27 | @property (nullable, nonatomic, strong) NSData *extendedData; ///< extended data (nil if no extended data)
28 | @end
29 |
30 | /**
31 | Storage type, indicated where the `YYKVStorageItem.value` stored.
32 |
33 | @discussion Typically, write data to sqlite is faster than extern file, but
34 | reading performance is dependent on data size. In my test (on iPhone 6 64G),
35 | read data from extern file is faster than from sqlite when the data is larger
36 | than 20KB.
37 |
38 | * If you want to store large number of small datas (such as contacts cache),
39 | use YYKVStorageTypeSQLite to get better performance.
40 | * If you want to store large files (such as image cache),
41 | use YYKVStorageTypeFile to get better performance.
42 | * You can use YYKVStorageTypeMixed and choice your storage type for each item.
43 |
44 | See for more information.
45 | */
46 | typedef NS_ENUM(NSUInteger, YYKVStorageType) {
47 |
48 | /// The `value` is stored as a file in file system.
49 | YYKVStorageTypeFile = 0,
50 |
51 | /// The `value` is stored in sqlite with blob type.
52 | YYKVStorageTypeSQLite = 1,
53 |
54 | /// The `value` is stored in file system or sqlite based on your choice.
55 | YYKVStorageTypeMixed = 2,
56 | };
57 |
58 |
59 |
60 | /**
61 | YYKVStorage is a key-value storage based on sqlite and file system.
62 | Typically, you should not use this class directly.
63 |
64 | @discussion The designated initializer for YYKVStorage is `initWithPath:type:`.
65 | After initialized, a directory is created based on the `path` to hold key-value data.
66 | Once initialized you should not read or write this directory without the instance.
67 |
68 | You may compile the latest version of sqlite and ignore the libsqlite3.dylib in
69 | iOS system to get 2x~4x speed up.
70 |
71 | @warning The instance of this class is *NOT* thread safe, you need to make sure
72 | that there's only one thread to access the instance at the same time. If you really
73 | need to process large amounts of data in multi-thread, you should split the data
74 | to multiple KVStorage instance (sharding).
75 | */
76 | @interface YYKVStorage : NSObject
77 |
78 | #pragma mark - Attribute
79 | ///=============================================================================
80 | /// @name Attribute
81 | ///=============================================================================
82 |
83 | @property (nonatomic, readonly) NSString *path; ///< The path of this storage.
84 | @property (nonatomic, readonly) YYKVStorageType type; ///< The type of this storage.
85 | @property (nonatomic) BOOL errorLogsEnabled; ///< Set `YES` to enable error logs for debug.
86 |
87 | #pragma mark - Initializer
88 | ///=============================================================================
89 | /// @name Initializer
90 | ///=============================================================================
91 | - (instancetype)init UNAVAILABLE_ATTRIBUTE;
92 | + (instancetype)new UNAVAILABLE_ATTRIBUTE;
93 |
94 | /**
95 | The designated initializer.
96 |
97 | @param path Full path of a directory in which the storage will write data. If
98 | the directory is not exists, it will try to create one, otherwise it will
99 | read the data in this directory.
100 | @param type The storage type. After first initialized you should not change the
101 | type of the specified path.
102 | @return A new storage object, or nil if an error occurs.
103 | @warning Multiple instances with the same path will make the storage unstable.
104 | */
105 | - (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER;
106 |
107 |
108 | #pragma mark - Save Items
109 | ///=============================================================================
110 | /// @name Save Items
111 | ///=============================================================================
112 |
113 | /**
114 | Save an item or update the item with 'key' if it already exists.
115 |
116 | @discussion This method will save the item.key, item.value, item.filename and
117 | item.extendedData to disk or sqlite, other properties will be ignored. item.key
118 | and item.value should not be empty (nil or zero length).
119 |
120 | If the `type` is YYKVStorageTypeFile, then the item.filename should not be empty.
121 | If the `type` is YYKVStorageTypeSQLite, then the item.filename will be ignored.
122 | It the `type` is YYKVStorageTypeMixed, then the item.value will be saved to file
123 | system if the item.filename is not empty, otherwise it will be saved to sqlite.
124 |
125 | @param item An item.
126 | @return Whether succeed.
127 | */
128 | - (BOOL)saveItem:(YYKVStorageItem *)item;
129 |
130 | /**
131 | Save an item or update the item with 'key' if it already exists.
132 |
133 | @discussion This method will save the key-value pair to sqlite. If the `type` is
134 | YYKVStorageTypeFile, then this method will failed.
135 |
136 | @param key The key, should not be empty (nil or zero length).
137 | @param value The key, should not be empty (nil or zero length).
138 | @return Whether succeed.
139 | */
140 | - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value;
141 |
142 | /**
143 | Save an item or update the item with 'key' if it already exists.
144 |
145 | @discussion
146 | If the `type` is YYKVStorageTypeFile, then the `filename` should not be empty.
147 | If the `type` is YYKVStorageTypeSQLite, then the `filename` will be ignored.
148 | It the `type` is YYKVStorageTypeMixed, then the `value` will be saved to file
149 | system if the `filename` is not empty, otherwise it will be saved to sqlite.
150 |
151 | @param key The key, should not be empty (nil or zero length).
152 | @param value The key, should not be empty (nil or zero length).
153 | @param filename The filename.
154 | @param extendedData The extended data for this item (pass nil to ignore it).
155 |
156 | @return Whether succeed.
157 | */
158 | - (BOOL)saveItemWithKey:(NSString *)key
159 | value:(NSData *)value
160 | filename:(nullable NSString *)filename
161 | extendedData:(nullable NSData *)extendedData;
162 |
163 | #pragma mark - Remove Items
164 | ///=============================================================================
165 | /// @name Remove Items
166 | ///=============================================================================
167 |
168 | /**
169 | Remove an item with 'key'.
170 |
171 | @param key The item's key.
172 | @return Whether succeed.
173 | */
174 | - (BOOL)removeItemForKey:(NSString *)key;
175 |
176 | /**
177 | Remove items with an array of keys.
178 |
179 | @param keys An array of specified keys.
180 |
181 | @return Whether succeed.
182 | */
183 | - (BOOL)removeItemForKeys:(NSArray *)keys;
184 |
185 | /**
186 | Remove all items which `value` is larger than a specified size.
187 |
188 | @param size The maximum size in bytes.
189 | @return Whether succeed.
190 | */
191 | - (BOOL)removeItemsLargerThanSize:(int)size;
192 |
193 | /**
194 | Remove all items which last access time is earlier than a specified timestamp.
195 |
196 | @param time The specified unix timestamp.
197 | @return Whether succeed.
198 | */
199 | - (BOOL)removeItemsEarlierThanTime:(int)time;
200 |
201 | /**
202 | Remove items to make the total size not larger than a specified size.
203 | The least recently used (LRU) items will be removed first.
204 |
205 | @param maxSize The specified size in bytes.
206 | @return Whether succeed.
207 | */
208 | - (BOOL)removeItemsToFitSize:(int)maxSize;
209 |
210 | /**
211 | Remove items to make the total count not larger than a specified count.
212 | The least recently used (LRU) items will be removed first.
213 |
214 | @param maxCount The specified item count.
215 | @return Whether succeed.
216 | */
217 | - (BOOL)removeItemsToFitCount:(int)maxCount;
218 |
219 | /**
220 | Remove all items in background queue.
221 |
222 | @discussion This method will remove the files and sqlite database to a trash
223 | folder, and then clear the folder in background queue. So this method is much
224 | faster than `removeAllItemsWithProgressBlock:endBlock:`.
225 |
226 | @return Whether succeed.
227 | */
228 | - (BOOL)removeAllItems;
229 |
230 | /**
231 | Remove all items.
232 |
233 | @warning You should not send message to this instance in these blocks.
234 | @param progress This block will be invoked during removing, pass nil to ignore.
235 | @param end This block will be invoked at the end, pass nil to ignore.
236 | */
237 | - (void)removeAllItemsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
238 | endBlock:(nullable void(^)(BOOL error))end;
239 |
240 |
241 | #pragma mark - Get Items
242 | ///=============================================================================
243 | /// @name Get Items
244 | ///=============================================================================
245 |
246 | /**
247 | Get item with a specified key.
248 |
249 | @param key A specified key.
250 | @return Item for the key, or nil if not exists / error occurs.
251 | */
252 | - (nullable YYKVStorageItem *)getItemForKey:(NSString *)key;
253 |
254 | /**
255 | Get item information with a specified key.
256 | The `value` in this item will be ignored.
257 |
258 | @param key A specified key.
259 | @return Item information for the key, or nil if not exists / error occurs.
260 | */
261 | - (nullable YYKVStorageItem *)getItemInfoForKey:(NSString *)key;
262 |
263 | /**
264 | Get item value with a specified key.
265 |
266 | @param key A specified key.
267 | @return Item's value, or nil if not exists / error occurs.
268 | */
269 | - (nullable NSData *)getItemValueForKey:(NSString *)key;
270 |
271 | /**
272 | Get items with an array of keys.
273 |
274 | @param keys An array of specified keys.
275 | @return An array of `YYKVStorageItem`, or nil if not exists / error occurs.
276 | */
277 | - (nullable NSArray *)getItemForKeys:(NSArray *)keys;
278 |
279 | /**
280 | Get item infomartions with an array of keys.
281 | The `value` in items will be ignored.
282 |
283 | @param keys An array of specified keys.
284 | @return An array of `YYKVStorageItem`, or nil if not exists / error occurs.
285 | */
286 | - (nullable NSArray *)getItemInfoForKeys:(NSArray *)keys;
287 |
288 | /**
289 | Get items value with an array of keys.
290 |
291 | @param keys An array of specified keys.
292 | @return A dictionary which key is 'key' and value is 'value', or nil if not
293 | exists / error occurs.
294 | */
295 | - (nullable NSDictionary *)getItemValueForKeys:(NSArray *)keys;
296 |
297 | #pragma mark - Get Storage Status
298 | ///=============================================================================
299 | /// @name Get Storage Status
300 | ///=============================================================================
301 |
302 | /**
303 | Whether an item exists for a specified key.
304 |
305 | @param key A specified key.
306 |
307 | @return `YES` if there's an item exists for the key, `NO` if not exists or an error occurs.
308 | */
309 | - (BOOL)itemExistsForKey:(NSString *)key;
310 |
311 | /**
312 | Get total item count.
313 | @return Total item count, -1 when an error occurs.
314 | */
315 | - (int)getItemsCount;
316 |
317 | /**
318 | Get item value's total size in bytes.
319 | @return Total size in bytes, -1 when an error occurs.
320 | */
321 | - (int)getItemsSize;
322 |
323 | @end
324 |
325 | NS_ASSUME_NONNULL_END
326 |
--------------------------------------------------------------------------------
/YYCache/YYMemoryCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // YYMemoryCache.h
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/2/7.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | /**
17 | YYMemoryCache is a fast in-memory cache that stores key-value pairs.
18 | In contrast to NSDictionary, keys are retained and not copied.
19 | The API and performance is similar to `NSCache`, all methods are thread-safe.
20 |
21 | YYMemoryCache objects differ from NSCache in a few ways:
22 |
23 | * It uses LRU (least-recently-used) to remove objects; NSCache's eviction method
24 | is non-deterministic.
25 | * It can be controlled by cost, count and age; NSCache's limits are imprecise.
26 | * It can be configured to automatically evict objects when receive memory
27 | warning or app enter background.
28 |
29 | The time of `Access Methods` in YYMemoryCache is typically in constant time (O(1)).
30 | */
31 | @interface YYMemoryCache : NSObject
32 |
33 | #pragma mark - Attribute
34 | ///=============================================================================
35 | /// @name Attribute
36 | ///=============================================================================
37 |
38 | /** The name of the cache. Default is nil. */
39 | @property (nullable, copy) NSString *name;
40 |
41 | /** The number of objects in the cache (read-only) */
42 | @property (readonly) NSUInteger totalCount;
43 |
44 | /** The total cost of objects in the cache (read-only). */
45 | @property (readonly) NSUInteger totalCost;
46 |
47 |
48 | #pragma mark - Limit
49 | ///=============================================================================
50 | /// @name Limit
51 | ///=============================================================================
52 |
53 | /**
54 | The maximum number of objects the cache should hold.
55 |
56 | @discussion The default value is NSUIntegerMax, which means no limit.
57 | This is not a strict limit—if the cache goes over the limit, some objects in the
58 | cache could be evicted later in backgound thread.
59 | */
60 | @property NSUInteger countLimit;
61 |
62 | /**
63 | The maximum total cost that the cache can hold before it starts evicting objects.
64 |
65 | @discussion The default value is NSUIntegerMax, which means no limit.
66 | This is not a strict limit—if the cache goes over the limit, some objects in the
67 | cache could be evicted later in backgound thread.
68 | */
69 | @property NSUInteger costLimit;
70 |
71 | /**
72 | The maximum expiry time of objects in cache.
73 |
74 | @discussion The default value is DBL_MAX, which means no limit.
75 | This is not a strict limit—if an object goes over the limit, the object could
76 | be evicted later in backgound thread.
77 | */
78 | @property NSTimeInterval ageLimit;
79 |
80 | /**
81 | The auto trim check time interval in seconds. Default is 5.0.
82 |
83 | @discussion The cache holds an internal timer to check whether the cache reaches
84 | its limits, and if the limit is reached, it begins to evict objects.
85 | */
86 | @property NSTimeInterval autoTrimInterval;
87 |
88 | /**
89 | If `YES`, the cache will remove all objects when the app receives a memory warning.
90 | The default value is `YES`.
91 | */
92 | @property BOOL shouldRemoveAllObjectsOnMemoryWarning;
93 |
94 | /**
95 | If `YES`, The cache will remove all objects when the app enter background.
96 | The default value is `YES`.
97 | */
98 | @property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
99 |
100 | /**
101 | A block to be executed when the app receives a memory warning.
102 | The default value is nil.
103 | */
104 | @property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
105 |
106 | /**
107 | A block to be executed when the app enter background.
108 | The default value is nil.
109 | */
110 | @property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
111 |
112 | /**
113 | If `YES`, the key-value pair will be released on main thread, otherwise on
114 | background thread. Default is NO.
115 |
116 | @discussion You may set this value to `YES` if the key-value object contains
117 | the instance which should be released in main thread (such as UIView/CALayer).
118 | */
119 | @property BOOL releaseOnMainThread;
120 |
121 | /**
122 | If `YES`, the key-value pair will be released asynchronously to avoid blocking
123 | the access methods, otherwise it will be released in the access method
124 | (such as removeObjectForKey:). Default is YES.
125 | */
126 | @property BOOL releaseAsynchronously;
127 |
128 |
129 | #pragma mark - Access Methods
130 | ///=============================================================================
131 | /// @name Access Methods
132 | ///=============================================================================
133 |
134 | /**
135 | Returns a Boolean value that indicates whether a given key is in cache.
136 |
137 | @param key An object identifying the value. If nil, just return `NO`.
138 | @return Whether the key is in cache.
139 | */
140 | - (BOOL)containsObjectForKey:(id)key;
141 |
142 | /**
143 | Returns the value associated with a given key.
144 |
145 | @param key An object identifying the value. If nil, just return nil.
146 | @return The value associated with key, or nil if no value is associated with key.
147 | */
148 | - (nullable id)objectForKey:(id)key;
149 |
150 | /**
151 | Sets the value of the specified key in the cache (0 cost).
152 |
153 | @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
154 | @param key The key with which to associate the value. If nil, this method has no effect.
155 | @discussion Unlike an NSMutableDictionary object, a cache does not copy the key
156 | objects that are put into it.
157 | */
158 | - (void)setObject:(nullable id)object forKey:(id)key;
159 |
160 | /**
161 | Sets the value of the specified key in the cache, and associates the key-value
162 | pair with the specified cost.
163 |
164 | @param object The object to store in the cache. If nil, it calls `removeObjectForKey`.
165 | @param key The key with which to associate the value. If nil, this method has no effect.
166 | @param cost The cost with which to associate the key-value pair.
167 | @discussion Unlike an NSMutableDictionary object, a cache does not copy the key
168 | objects that are put into it.
169 | */
170 | - (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
171 |
172 | /**
173 | Removes the value of the specified key in the cache.
174 |
175 | @param key The key identifying the value to be removed. If nil, this method has no effect.
176 | */
177 | - (void)removeObjectForKey:(id)key;
178 |
179 | /**
180 | Empties the cache immediately.
181 | */
182 | - (void)removeAllObjects;
183 |
184 |
185 | #pragma mark - Trim
186 | ///=============================================================================
187 | /// @name Trim
188 | ///=============================================================================
189 |
190 | /**
191 | Removes objects from the cache with LRU, until the `totalCount` is below or equal to
192 | the specified value.
193 | @param count The total count allowed to remain after the cache has been trimmed.
194 | */
195 | - (void)trimToCount:(NSUInteger)count;
196 |
197 | /**
198 | Removes objects from the cache with LRU, until the `totalCost` is or equal to
199 | the specified value.
200 | @param cost The total cost allowed to remain after the cache has been trimmed.
201 | */
202 | - (void)trimToCost:(NSUInteger)cost;
203 |
204 | /**
205 | Removes objects from the cache with LRU, until all expiry objects removed by the
206 | specified value.
207 | @param age The maximum age (in seconds) of objects.
208 | */
209 | - (void)trimToAge:(NSTimeInterval)age;
210 |
211 | @end
212 |
213 | NS_ASSUME_NONNULL_END
214 |
--------------------------------------------------------------------------------
/YYCache/YYMemoryCache.m:
--------------------------------------------------------------------------------
1 | //
2 | // YYMemoryCache.m
3 | // YYCache
4 | //
5 | // Created by ibireme on 15/2/7.
6 | // Copyright (c) 2015 ibireme.
7 | //
8 | // This source code is licensed under the MIT-style license found in the
9 | // LICENSE file in the root directory of this source tree.
10 | //
11 |
12 | #import "YYMemoryCache.h"
13 | #import
14 | #import
15 | #import
16 | #import
17 |
18 |
19 | static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
20 | return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
21 | }
22 |
23 | /**
24 | A node in linked map.
25 | Typically, you should not use this class directly.
26 | */
27 | @interface _YYLinkedMapNode : NSObject {
28 | @package
29 | __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
30 | __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
31 | id _key;
32 | id _value;
33 | NSUInteger _cost;
34 | NSTimeInterval _time;
35 | }
36 | @end
37 |
38 | @implementation _YYLinkedMapNode
39 | @end
40 |
41 |
42 | /**
43 | A linked map used by YYMemoryCache.
44 | It's not thread-safe and does not validate the parameters.
45 |
46 | Typically, you should not use this class directly.
47 | */
48 | @interface _YYLinkedMap : NSObject {
49 | @package
50 | CFMutableDictionaryRef _dic; // do not set object directly
51 | NSUInteger _totalCost;
52 | NSUInteger _totalCount;
53 | _YYLinkedMapNode *_head; // MRU, do not change it directly
54 | _YYLinkedMapNode *_tail; // LRU, do not change it directly
55 | BOOL _releaseOnMainThread;
56 | BOOL _releaseAsynchronously;
57 | }
58 |
59 | /// Insert a node at head and update the total cost.
60 | /// Node and node.key should not be nil.
61 | - (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
62 |
63 | /// Bring a inner node to header.
64 | /// Node should already inside the dic.
65 | - (void)bringNodeToHead:(_YYLinkedMapNode *)node;
66 |
67 | /// Remove a inner node and update the total cost.
68 | /// Node should already inside the dic.
69 | - (void)removeNode:(_YYLinkedMapNode *)node;
70 |
71 | /// Remove tail node if exist.
72 | - (_YYLinkedMapNode *)removeTailNode;
73 |
74 | /// Remove all node in background queue.
75 | - (void)removeAll;
76 |
77 | @end
78 |
79 | @implementation _YYLinkedMap
80 |
81 | - (instancetype)init {
82 | self = [super init];
83 | _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
84 | _releaseOnMainThread = NO;
85 | _releaseAsynchronously = YES;
86 | return self;
87 | }
88 |
89 | - (void)dealloc {
90 | CFRelease(_dic);
91 | }
92 |
93 | - (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
94 | CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
95 | _totalCost += node->_cost;
96 | _totalCount++;
97 | if (_head) {
98 | node->_next = _head;
99 | _head->_prev = node;
100 | _head = node;
101 | } else {
102 | _head = _tail = node;
103 | }
104 | }
105 |
106 | - (void)bringNodeToHead:(_YYLinkedMapNode *)node {
107 | if (_head == node) return;
108 |
109 | if (_tail == node) {
110 | _tail = node->_prev;
111 | _tail->_next = nil;
112 | } else {
113 | node->_next->_prev = node->_prev;
114 | node->_prev->_next = node->_next;
115 | }
116 | node->_next = _head;
117 | node->_prev = nil;
118 | _head->_prev = node;
119 | _head = node;
120 | }
121 |
122 | - (void)removeNode:(_YYLinkedMapNode *)node {
123 | CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
124 | _totalCost -= node->_cost;
125 | _totalCount--;
126 | if (node->_next) node->_next->_prev = node->_prev;
127 | if (node->_prev) node->_prev->_next = node->_next;
128 | if (_head == node) _head = node->_next;
129 | if (_tail == node) _tail = node->_prev;
130 | }
131 |
132 | - (_YYLinkedMapNode *)removeTailNode {
133 | if (!_tail) return nil;
134 | _YYLinkedMapNode *tail = _tail;
135 | CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
136 | _totalCost -= _tail->_cost;
137 | _totalCount--;
138 | if (_head == _tail) {
139 | _head = _tail = nil;
140 | } else {
141 | _tail = _tail->_prev;
142 | _tail->_next = nil;
143 | }
144 | return tail;
145 | }
146 |
147 | - (void)removeAll {
148 | _totalCost = 0;
149 | _totalCount = 0;
150 | _head = nil;
151 | _tail = nil;
152 | if (CFDictionaryGetCount(_dic) > 0) {
153 | CFMutableDictionaryRef holder = _dic;
154 | _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
155 |
156 | if (_releaseAsynchronously) {
157 | dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
158 | dispatch_async(queue, ^{
159 | CFRelease(holder); // hold and release in specified queue
160 | });
161 | } else if (_releaseOnMainThread && !pthread_main_np()) {
162 | dispatch_async(dispatch_get_main_queue(), ^{
163 | CFRelease(holder); // hold and release in specified queue
164 | });
165 | } else {
166 | CFRelease(holder);
167 | }
168 | }
169 | }
170 |
171 | @end
172 |
173 |
174 |
175 | @implementation YYMemoryCache {
176 | pthread_mutex_t _lock;
177 | _YYLinkedMap *_lru;
178 | dispatch_queue_t _queue;
179 | }
180 |
181 | - (void)_trimRecursively {
182 | __weak typeof(self) _self = self;
183 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
184 | __strong typeof(_self) self = _self;
185 | if (!self) return;
186 | [self _trimInBackground];
187 | [self _trimRecursively];
188 | });
189 | }
190 |
191 | - (void)_trimInBackground {
192 | dispatch_async(_queue, ^{
193 | [self _trimToCost:self->_costLimit];
194 | [self _trimToCount:self->_countLimit];
195 | [self _trimToAge:self->_ageLimit];
196 | });
197 | }
198 |
199 | - (void)_trimToCost:(NSUInteger)costLimit {
200 | BOOL finish = NO;
201 | pthread_mutex_lock(&_lock);
202 | if (costLimit == 0) {
203 | [_lru removeAll];
204 | finish = YES;
205 | } else if (_lru->_totalCost <= costLimit) {
206 | finish = YES;
207 | }
208 | pthread_mutex_unlock(&_lock);
209 | if (finish) return;
210 |
211 | NSMutableArray *holder = [NSMutableArray new];
212 | while (!finish) {
213 | if (pthread_mutex_trylock(&_lock) == 0) {
214 | if (_lru->_totalCost > costLimit) {
215 | _YYLinkedMapNode *node = [_lru removeTailNode];
216 | if (node) [holder addObject:node];
217 | } else {
218 | finish = YES;
219 | }
220 | pthread_mutex_unlock(&_lock);
221 | } else {
222 | usleep(10 * 1000); //10 ms
223 | }
224 | }
225 | if (holder.count) {
226 | dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
227 | dispatch_async(queue, ^{
228 | [holder count]; // release in queue
229 | });
230 | }
231 | }
232 |
233 | - (void)_trimToCount:(NSUInteger)countLimit {
234 | BOOL finish = NO;
235 | pthread_mutex_lock(&_lock);
236 | if (countLimit == 0) {
237 | [_lru removeAll];
238 | finish = YES;
239 | } else if (_lru->_totalCount <= countLimit) {
240 | finish = YES;
241 | }
242 | pthread_mutex_unlock(&_lock);
243 | if (finish) return;
244 |
245 | NSMutableArray *holder = [NSMutableArray new];
246 | while (!finish) {
247 | if (pthread_mutex_trylock(&_lock) == 0) {
248 | if (_lru->_totalCount > countLimit) {
249 | _YYLinkedMapNode *node = [_lru removeTailNode];
250 | if (node) [holder addObject:node];
251 | } else {
252 | finish = YES;
253 | }
254 | pthread_mutex_unlock(&_lock);
255 | } else {
256 | usleep(10 * 1000); //10 ms
257 | }
258 | }
259 | if (holder.count) {
260 | dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
261 | dispatch_async(queue, ^{
262 | [holder count]; // release in queue
263 | });
264 | }
265 | }
266 |
267 | - (void)_trimToAge:(NSTimeInterval)ageLimit {
268 | BOOL finish = NO;
269 | NSTimeInterval now = CACurrentMediaTime();
270 | pthread_mutex_lock(&_lock);
271 | if (ageLimit <= 0) {
272 | [_lru removeAll];
273 | finish = YES;
274 | } else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
275 | finish = YES;
276 | }
277 | pthread_mutex_unlock(&_lock);
278 | if (finish) return;
279 |
280 | NSMutableArray *holder = [NSMutableArray new];
281 | while (!finish) {
282 | if (pthread_mutex_trylock(&_lock) == 0) {
283 | if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
284 | _YYLinkedMapNode *node = [_lru removeTailNode];
285 | if (node) [holder addObject:node];
286 | } else {
287 | finish = YES;
288 | }
289 | pthread_mutex_unlock(&_lock);
290 | } else {
291 | usleep(10 * 1000); //10 ms
292 | }
293 | }
294 | if (holder.count) {
295 | dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
296 | dispatch_async(queue, ^{
297 | [holder count]; // release in queue
298 | });
299 | }
300 | }
301 |
302 | - (void)_appDidReceiveMemoryWarningNotification {
303 | if (self.didReceiveMemoryWarningBlock) {
304 | self.didReceiveMemoryWarningBlock(self);
305 | }
306 | if (self.shouldRemoveAllObjectsOnMemoryWarning) {
307 | [self removeAllObjects];
308 | }
309 | }
310 |
311 | - (void)_appDidEnterBackgroundNotification {
312 | if (self.didEnterBackgroundBlock) {
313 | self.didEnterBackgroundBlock(self);
314 | }
315 | if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
316 | [self removeAllObjects];
317 | }
318 | }
319 |
320 | #pragma mark - public
321 |
322 | - (instancetype)init {
323 | self = super.init;
324 | pthread_mutex_init(&_lock, NULL);
325 | _lru = [_YYLinkedMap new];
326 | _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
327 |
328 | _countLimit = NSUIntegerMax;
329 | _costLimit = NSUIntegerMax;
330 | _ageLimit = DBL_MAX;
331 | _autoTrimInterval = 5.0;
332 | _shouldRemoveAllObjectsOnMemoryWarning = YES;
333 | _shouldRemoveAllObjectsWhenEnteringBackground = YES;
334 |
335 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
336 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
337 |
338 | [self _trimRecursively];
339 | return self;
340 | }
341 |
342 | - (void)dealloc {
343 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
344 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
345 | [_lru removeAll];
346 | pthread_mutex_destroy(&_lock);
347 | }
348 |
349 | - (NSUInteger)totalCount {
350 | pthread_mutex_lock(&_lock);
351 | NSUInteger count = _lru->_totalCount;
352 | pthread_mutex_unlock(&_lock);
353 | return count;
354 | }
355 |
356 | - (NSUInteger)totalCost {
357 | pthread_mutex_lock(&_lock);
358 | NSUInteger totalCost = _lru->_totalCost;
359 | pthread_mutex_unlock(&_lock);
360 | return totalCost;
361 | }
362 |
363 | - (BOOL)releaseOnMainThread {
364 | pthread_mutex_lock(&_lock);
365 | BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
366 | pthread_mutex_unlock(&_lock);
367 | return releaseOnMainThread;
368 | }
369 |
370 | - (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
371 | pthread_mutex_lock(&_lock);
372 | _lru->_releaseOnMainThread = releaseOnMainThread;
373 | pthread_mutex_unlock(&_lock);
374 | }
375 |
376 | - (BOOL)releaseAsynchronously {
377 | pthread_mutex_lock(&_lock);
378 | BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
379 | pthread_mutex_unlock(&_lock);
380 | return releaseAsynchronously;
381 | }
382 |
383 | - (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
384 | pthread_mutex_lock(&_lock);
385 | _lru->_releaseAsynchronously = releaseAsynchronously;
386 | pthread_mutex_unlock(&_lock);
387 | }
388 |
389 | - (BOOL)containsObjectForKey:(id)key {
390 | if (!key) return NO;
391 | pthread_mutex_lock(&_lock);
392 | BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
393 | pthread_mutex_unlock(&_lock);
394 | return contains;
395 | }
396 |
397 | - (id)objectForKey:(id)key {
398 | if (!key) return nil;
399 | pthread_mutex_lock(&_lock);
400 | _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
401 | if (node) {
402 | node->_time = CACurrentMediaTime();
403 | [_lru bringNodeToHead:node];
404 | }
405 | pthread_mutex_unlock(&_lock);
406 | return node ? node->_value : nil;
407 | }
408 |
409 | - (void)setObject:(id)object forKey:(id)key {
410 | [self setObject:object forKey:key withCost:0];
411 | }
412 |
413 | - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
414 | if (!key) return;
415 | if (!object) {
416 | [self removeObjectForKey:key];
417 | return;
418 | }
419 | pthread_mutex_lock(&_lock);
420 | _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
421 | NSTimeInterval now = CACurrentMediaTime();
422 | if (node) {
423 | _lru->_totalCost -= node->_cost;
424 | _lru->_totalCost += cost;
425 | node->_cost = cost;
426 | node->_time = now;
427 | node->_value = object;
428 | [_lru bringNodeToHead:node];
429 | } else {
430 | node = [_YYLinkedMapNode new];
431 | node->_cost = cost;
432 | node->_time = now;
433 | node->_key = key;
434 | node->_value = object;
435 | [_lru insertNodeAtHead:node];
436 | }
437 | if (_lru->_totalCost > _costLimit) {
438 | dispatch_async(_queue, ^{
439 | [self trimToCost:_costLimit];
440 | });
441 | }
442 | if (_lru->_totalCount > _countLimit) {
443 | _YYLinkedMapNode *node = [_lru removeTailNode];
444 | if (_lru->_releaseAsynchronously) {
445 | dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
446 | dispatch_async(queue, ^{
447 | [node class]; //hold and release in queue
448 | });
449 | } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
450 | dispatch_async(dispatch_get_main_queue(), ^{
451 | [node class]; //hold and release in queue
452 | });
453 | }
454 | }
455 | pthread_mutex_unlock(&_lock);
456 | }
457 |
458 | - (void)removeObjectForKey:(id)key {
459 | if (!key) return;
460 | pthread_mutex_lock(&_lock);
461 | _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
462 | if (node) {
463 | [_lru removeNode:node];
464 | if (_lru->_releaseAsynchronously) {
465 | dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
466 | dispatch_async(queue, ^{
467 | [node class]; //hold and release in queue
468 | });
469 | } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
470 | dispatch_async(dispatch_get_main_queue(), ^{
471 | [node class]; //hold and release in queue
472 | });
473 | }
474 | }
475 | pthread_mutex_unlock(&_lock);
476 | }
477 |
478 | - (void)removeAllObjects {
479 | pthread_mutex_lock(&_lock);
480 | [_lru removeAll];
481 | pthread_mutex_unlock(&_lock);
482 | }
483 |
484 | - (void)trimToCount:(NSUInteger)count {
485 | if (count == 0) {
486 | [self removeAllObjects];
487 | return;
488 | }
489 | [self _trimToCount:count];
490 | }
491 |
492 | - (void)trimToCost:(NSUInteger)cost {
493 | [self _trimToCost:cost];
494 | }
495 |
496 | - (void)trimToAge:(NSTimeInterval)age {
497 | [self _trimToAge:age];
498 | }
499 |
500 | - (NSString *)description {
501 | if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
502 | else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
503 | }
504 |
505 | @end
506 |
--------------------------------------------------------------------------------