├── .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 | [![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYCache/master/LICENSE)  5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)  6 | [![CocoaPods](http://img.shields.io/cocoapods/v/YYCache.svg?style=flat)](http://cocoapods.org/pods/YYCache)  7 | [![CocoaPods](http://img.shields.io/cocoapods/p/YYCache.svg?style=flat)](http://cocoadocs.org/docsets/YYCache)  8 | [![Support](https://img.shields.io/badge/support-iOS%206%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/)  9 | [![Build Status](https://travis-ci.org/ibireme/YYCache.svg?branch=master)](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 | ![Memory cache benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_memory.png 18 | ) 19 | 20 | ![Disk benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_disk.png 21 | ) 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 | ![Memory cache benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_memory.png 99 | ) 100 | 101 | iPhone 6 上,磁盘缓存每秒响应次数 (越高越好): 102 | ![Disk benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_disk.png 103 | ) 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 | --------------------------------------------------------------------------------