├── .gitignore ├── .swift-version ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── Demo.xcscmblueprint │ │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── quanma.xcuserdatad │ │ └── xcschemes │ │ ├── Demo.xcscheme │ │ └── xcschememanagement.plist └── Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Demo-Bridging-Header.h │ ├── Info.plist │ └── ViewController.swift ├── Framework ├── Track.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Track.xcscheme └── Track │ ├── Info.plist │ └── Track.h ├── LICENSE ├── Track.podspec ├── Track ├── Cache.swift ├── DiskCache.swift ├── LinkedList.swift └── MemoryCache.swift ├── logo.png ├── readme.md └── 如果你在天朝.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D4485AAF1CEF050700EC8C21 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4485AAE1CEF050700EC8C21 /* LinkedList.swift */; }; 11 | D481BDC61CEC5ACB0010A367 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D481BDC31CEC5ACB0010A367 /* Cache.swift */; }; 12 | D481BDC71CEC5ACB0010A367 /* DiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D481BDC41CEC5ACB0010A367 /* DiskCache.swift */; }; 13 | D481BDC81CEC5ACB0010A367 /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D481BDC51CEC5ACB0010A367 /* MemoryCache.swift */; }; 14 | D4ABC2231CEB49AA00BF78D3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4ABC2221CEB49AA00BF78D3 /* AppDelegate.swift */; }; 15 | D4ABC2251CEB49AA00BF78D3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4ABC2241CEB49AA00BF78D3 /* ViewController.swift */; }; 16 | D4ABC2281CEB49AA00BF78D3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D4ABC2261CEB49AA00BF78D3 /* Main.storyboard */; }; 17 | D4ABC22A1CEB49AA00BF78D3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D4ABC2291CEB49AA00BF78D3 /* Assets.xcassets */; }; 18 | D4ABC22D1CEB49AA00BF78D3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D4ABC22B1CEB49AA00BF78D3 /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | D4485AAE1CEF050700EC8C21 /* LinkedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkedList.swift; sourceTree = ""; }; 23 | D481BDC31CEC5ACB0010A367 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = ""; }; 24 | D481BDC41CEC5ACB0010A367 /* DiskCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskCache.swift; sourceTree = ""; }; 25 | D481BDC51CEC5ACB0010A367 /* MemoryCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = ""; }; 26 | D4ABC21F1CEB49AA00BF78D3 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | D4ABC2221CEB49AA00BF78D3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | D4ABC2241CEB49AA00BF78D3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 29 | D4ABC2271CEB49AA00BF78D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | D4ABC2291CEB49AA00BF78D3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | D4ABC22C1CEB49AA00BF78D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | D4ABC22E1CEB49AA00BF78D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | D4ABC2851CEC45F200BF78D3 /* Demo-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Demo-Bridging-Header.h"; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | D4ABC21C1CEB49AA00BF78D3 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | D481BDC21CEC5ACB0010A367 /* Track */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | D4485AAE1CEF050700EC8C21 /* LinkedList.swift */, 51 | D481BDC31CEC5ACB0010A367 /* Cache.swift */, 52 | D481BDC41CEC5ACB0010A367 /* DiskCache.swift */, 53 | D481BDC51CEC5ACB0010A367 /* MemoryCache.swift */, 54 | ); 55 | name = Track; 56 | path = ../Track; 57 | sourceTree = ""; 58 | }; 59 | D4ABC2161CEB49AA00BF78D3 = { 60 | isa = PBXGroup; 61 | children = ( 62 | D481BDC21CEC5ACB0010A367 /* Track */, 63 | D4ABC2211CEB49AA00BF78D3 /* Demo */, 64 | D4ABC2201CEB49AA00BF78D3 /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | D4ABC2201CEB49AA00BF78D3 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | D4ABC21F1CEB49AA00BF78D3 /* Demo.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | D4ABC2211CEB49AA00BF78D3 /* Demo */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | D4ABC2221CEB49AA00BF78D3 /* AppDelegate.swift */, 80 | D4ABC2241CEB49AA00BF78D3 /* ViewController.swift */, 81 | D4ABC2261CEB49AA00BF78D3 /* Main.storyboard */, 82 | D4ABC2291CEB49AA00BF78D3 /* Assets.xcassets */, 83 | D4ABC22B1CEB49AA00BF78D3 /* LaunchScreen.storyboard */, 84 | D4ABC22E1CEB49AA00BF78D3 /* Info.plist */, 85 | D4ABC2851CEC45F200BF78D3 /* Demo-Bridging-Header.h */, 86 | ); 87 | path = Demo; 88 | sourceTree = ""; 89 | }; 90 | /* End PBXGroup section */ 91 | 92 | /* Begin PBXNativeTarget section */ 93 | D4ABC21E1CEB49AA00BF78D3 /* Demo */ = { 94 | isa = PBXNativeTarget; 95 | buildConfigurationList = D4ABC2311CEB49AA00BF78D3 /* Build configuration list for PBXNativeTarget "Demo" */; 96 | buildPhases = ( 97 | D4ABC21B1CEB49AA00BF78D3 /* Sources */, 98 | D4ABC21C1CEB49AA00BF78D3 /* Frameworks */, 99 | D4ABC21D1CEB49AA00BF78D3 /* Resources */, 100 | ); 101 | buildRules = ( 102 | ); 103 | dependencies = ( 104 | ); 105 | name = Demo; 106 | productName = Demo; 107 | productReference = D4ABC21F1CEB49AA00BF78D3 /* Demo.app */; 108 | productType = "com.apple.product-type.application"; 109 | }; 110 | /* End PBXNativeTarget section */ 111 | 112 | /* Begin PBXProject section */ 113 | D4ABC2171CEB49AA00BF78D3 /* Project object */ = { 114 | isa = PBXProject; 115 | attributes = { 116 | LastSwiftUpdateCheck = 0730; 117 | LastUpgradeCheck = 1020; 118 | ORGANIZATIONNAME = "马权"; 119 | TargetAttributes = { 120 | D4ABC21E1CEB49AA00BF78D3 = { 121 | CreatedOnToolsVersion = 7.3; 122 | LastSwiftMigration = 0800; 123 | }; 124 | }; 125 | }; 126 | buildConfigurationList = D4ABC21A1CEB49AA00BF78D3 /* Build configuration list for PBXProject "Demo" */; 127 | compatibilityVersion = "Xcode 3.2"; 128 | developmentRegion = en; 129 | hasScannedForEncodings = 0; 130 | knownRegions = ( 131 | en, 132 | Base, 133 | ); 134 | mainGroup = D4ABC2161CEB49AA00BF78D3; 135 | productRefGroup = D4ABC2201CEB49AA00BF78D3 /* Products */; 136 | projectDirPath = ""; 137 | projectRoot = ""; 138 | targets = ( 139 | D4ABC21E1CEB49AA00BF78D3 /* Demo */, 140 | ); 141 | }; 142 | /* End PBXProject section */ 143 | 144 | /* Begin PBXResourcesBuildPhase section */ 145 | D4ABC21D1CEB49AA00BF78D3 /* Resources */ = { 146 | isa = PBXResourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | D4ABC22D1CEB49AA00BF78D3 /* LaunchScreen.storyboard in Resources */, 150 | D4ABC22A1CEB49AA00BF78D3 /* Assets.xcassets in Resources */, 151 | D4ABC2281CEB49AA00BF78D3 /* Main.storyboard in Resources */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXResourcesBuildPhase section */ 156 | 157 | /* Begin PBXSourcesBuildPhase section */ 158 | D4ABC21B1CEB49AA00BF78D3 /* Sources */ = { 159 | isa = PBXSourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | D4ABC2251CEB49AA00BF78D3 /* ViewController.swift in Sources */, 163 | D481BDC61CEC5ACB0010A367 /* Cache.swift in Sources */, 164 | D481BDC81CEC5ACB0010A367 /* MemoryCache.swift in Sources */, 165 | D4ABC2231CEB49AA00BF78D3 /* AppDelegate.swift in Sources */, 166 | D4485AAF1CEF050700EC8C21 /* LinkedList.swift in Sources */, 167 | D481BDC71CEC5ACB0010A367 /* DiskCache.swift in Sources */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXSourcesBuildPhase section */ 172 | 173 | /* Begin PBXVariantGroup section */ 174 | D4ABC2261CEB49AA00BF78D3 /* Main.storyboard */ = { 175 | isa = PBXVariantGroup; 176 | children = ( 177 | D4ABC2271CEB49AA00BF78D3 /* Base */, 178 | ); 179 | name = Main.storyboard; 180 | sourceTree = ""; 181 | }; 182 | D4ABC22B1CEB49AA00BF78D3 /* LaunchScreen.storyboard */ = { 183 | isa = PBXVariantGroup; 184 | children = ( 185 | D4ABC22C1CEB49AA00BF78D3 /* Base */, 186 | ); 187 | name = LaunchScreen.storyboard; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXVariantGroup section */ 191 | 192 | /* Begin XCBuildConfiguration section */ 193 | D4ABC22F1CEB49AA00BF78D3 /* Debug */ = { 194 | isa = XCBuildConfiguration; 195 | buildSettings = { 196 | ALWAYS_SEARCH_USER_PATHS = NO; 197 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 198 | CLANG_ANALYZER_NONNULL = YES; 199 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 200 | CLANG_CXX_LIBRARY = "libc++"; 201 | CLANG_ENABLE_MODULES = YES; 202 | CLANG_ENABLE_OBJC_ARC = YES; 203 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 204 | CLANG_WARN_BOOL_CONVERSION = YES; 205 | CLANG_WARN_COMMA = YES; 206 | CLANG_WARN_CONSTANT_CONVERSION = YES; 207 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 208 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 209 | CLANG_WARN_EMPTY_BODY = YES; 210 | CLANG_WARN_ENUM_CONVERSION = YES; 211 | CLANG_WARN_INFINITE_RECURSION = YES; 212 | CLANG_WARN_INT_CONVERSION = YES; 213 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 214 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 215 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 217 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 218 | CLANG_WARN_STRICT_PROTOTYPES = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNREACHABLE_CODE = YES; 221 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 222 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 223 | COPY_PHASE_STRIP = NO; 224 | DEBUG_INFORMATION_FORMAT = dwarf; 225 | ENABLE_STRICT_OBJC_MSGSEND = YES; 226 | ENABLE_TESTABILITY = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 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 = 9.3; 242 | MTL_ENABLE_DEBUG_INFO = YES; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 246 | TARGETED_DEVICE_FAMILY = "1,2"; 247 | }; 248 | name = Debug; 249 | }; 250 | D4ABC2301CEB49AA00BF78D3 /* Release */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 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_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 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_IMPLICIT_RETAIN_SELF = YES; 272 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNREACHABLE_CODE = YES; 278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 279 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 280 | COPY_PHASE_STRIP = NO; 281 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 282 | ENABLE_NS_ASSERTIONS = NO; 283 | ENABLE_STRICT_OBJC_MSGSEND = YES; 284 | GCC_C_LANGUAGE_STANDARD = gnu99; 285 | GCC_NO_COMMON_BLOCKS = YES; 286 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 287 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 288 | GCC_WARN_UNDECLARED_SELECTOR = YES; 289 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 290 | GCC_WARN_UNUSED_FUNCTION = YES; 291 | GCC_WARN_UNUSED_VARIABLE = YES; 292 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 293 | MTL_ENABLE_DEBUG_INFO = NO; 294 | SDKROOT = iphoneos; 295 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 296 | TARGETED_DEVICE_FAMILY = "1,2"; 297 | VALIDATE_PRODUCT = YES; 298 | }; 299 | name = Release; 300 | }; 301 | D4ABC2321CEB49AA00BF78D3 /* Debug */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 305 | INFOPLIST_FILE = Demo/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 307 | PRODUCT_BUNDLE_IDENTIFIER = "--.Demo"; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_OBJC_BRIDGING_HEADER = "Demo/Demo-Bridging-Header.h"; 310 | SWIFT_VERSION = 5.0; 311 | }; 312 | name = Debug; 313 | }; 314 | D4ABC2331CEB49AA00BF78D3 /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | INFOPLIST_FILE = Demo/Info.plist; 319 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 320 | PRODUCT_BUNDLE_IDENTIFIER = "--.Demo"; 321 | PRODUCT_NAME = "$(TARGET_NAME)"; 322 | SWIFT_OBJC_BRIDGING_HEADER = "Demo/Demo-Bridging-Header.h"; 323 | SWIFT_VERSION = 5.0; 324 | }; 325 | name = Release; 326 | }; 327 | /* End XCBuildConfiguration section */ 328 | 329 | /* Begin XCConfigurationList section */ 330 | D4ABC21A1CEB49AA00BF78D3 /* Build configuration list for PBXProject "Demo" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | D4ABC22F1CEB49AA00BF78D3 /* Debug */, 334 | D4ABC2301CEB49AA00BF78D3 /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | D4ABC2311CEB49AA00BF78D3 /* Build configuration list for PBXNativeTarget "Demo" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | D4ABC2321CEB49AA00BF78D3 /* Debug */, 343 | D4ABC2331CEB49AA00BF78D3 /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | /* End XCConfigurationList section */ 349 | }; 350 | rootObject = D4ABC2171CEB49AA00BF78D3 /* Project object */; 351 | } 352 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/Demo.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "83700F58C0165CC20774AC8861C87F9B96014D92+++EC72603", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++873D270" : 0, 8 | "087BB9BAF2D0E7BB0C8DABFA13897AFE5D08BEAF+++E7FD5BE" : 0, 9 | "673E0B6CD7FE3F49C266006074302612E319D4DF+++94D5352" : 0, 10 | "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++FE349AC" : 0, 11 | "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++D46231C" : 0, 12 | "83700F58C0165CC20774AC8861C87F9B96014D92+++EC72603" : 0 13 | }, 14 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F92D1B96-3B0B-4D89-B6DE-57ACBE74881D", 15 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 16 | "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++873D270" : "TMCache\/", 17 | "087BB9BAF2D0E7BB0C8DABFA13897AFE5D08BEAF+++E7FD5BE" : "YYCache\/", 18 | "673E0B6CD7FE3F49C266006074302612E319D4DF+++94D5352" : "Cache\/", 19 | "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++FE349AC" : "PINCache\/", 20 | "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++D46231C" : "TMCache\/", 21 | "83700F58C0165CC20774AC8861C87F9B96014D92+++EC72603" : "Track\/" 22 | }, 23 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Demo", 24 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 25 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Demo\/Demo.xcodeproj", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 27 | { 28 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/ibireme\/YYCache.git", 29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 30 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "087BB9BAF2D0E7BB0C8DABFA13897AFE5D08BEAF+++E7FD5BE" 31 | }, 32 | { 33 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/tumblr\/TMCache.git", 34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 35 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++873D270" 36 | }, 37 | { 38 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/pinterest\/PINCache.git", 39 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 40 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++D46231C" 41 | }, 42 | { 43 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/pinterest\/PINCache.git", 44 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 45 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "44B36422A7A7C3AD3DE414BC27AA89E3C0009BBA+++FE349AC" 46 | }, 47 | { 48 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/soffes\/Cache.git", 49 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 50 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "673E0B6CD7FE3F49C266006074302612E319D4DF+++94D5352" 51 | }, 52 | { 53 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/maquannene\/Track.git", 54 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 55 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "83700F58C0165CC20774AC8861C87F9B96014D92+++EC72603" 56 | } 57 | ] 58 | } -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/quanma.xcuserdatad/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/quanma.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Demo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | D4ABC21E1CEB49AA00BF78D3 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Demo 4 | // 5 | // Created by 马权 on 5/17/16. 6 | // Copyright © 2016 马权. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | let printTime: (() -> Void) -> Void = { 18 | let startTime: CFTimeInterval = CACurrentMediaTime() 19 | $0() 20 | let endTime: CFTimeInterval = CACurrentMediaTime() 21 | print((endTime - startTime) * 1000) 22 | } 23 | 24 | let time: UInt = 5 25 | 26 | // Track 27 | let cache: Cache = Cache.shareInstance 28 | 29 | // for i in 1 ... 5 { 30 | // cache.set(object: "\(i)", forKey: "\(i)") 31 | // } 32 | // 33 | for i in 6 ... 7 { 34 | cache.set(object: "\(i)" as NSCoding, forKey: "\(i)") 35 | } 36 | 37 | for object in cache { 38 | print(object) 39 | } 40 | 41 | cache.forEach { 42 | print($0) 43 | } 44 | 45 | let values = cache.map { return $0 } 46 | 47 | print(values) 48 | 49 | return true 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Demo/Demo/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 | -------------------------------------------------------------------------------- /Demo/Demo/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 | -------------------------------------------------------------------------------- /Demo/Demo/Demo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Demo-Bridging-Header.h 3 | // Demo 4 | // 5 | // Created by 马权 on 5/18/16. 6 | // Copyright © 2016 马权. All rights reserved. 7 | // 8 | 9 | #ifndef Demo_Bridging_Header_h 10 | #define Demo_Bridging_Header_h 11 | 12 | //#import "PINCache.h" 13 | //#import "TMCache.h" 14 | //#import "YYCache.h" 15 | 16 | #endif /* Demo_Bridging_Header_h */ 17 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Demo/Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Demo 4 | // 5 | // Created by 马权 on 5/17/16. 6 | // Copyright © 2016 马权. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Framework/Track.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D4C2ADF41CF5C1AE00FE5851 /* Track.h in Headers */ = {isa = PBXBuildFile; fileRef = D4C2ADF31CF5C1AE00FE5851 /* Track.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | D4C2ADFF1CF5C21200FE5851 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C2ADFB1CF5C21200FE5851 /* Cache.swift */; }; 12 | D4C2AE001CF5C21200FE5851 /* DiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C2ADFC1CF5C21200FE5851 /* DiskCache.swift */; }; 13 | D4C2AE011CF5C21200FE5851 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C2ADFD1CF5C21200FE5851 /* LinkedList.swift */; }; 14 | D4C2AE021CF5C21200FE5851 /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C2ADFE1CF5C21200FE5851 /* MemoryCache.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | D4C2ADF01CF5C1AE00FE5851 /* Track.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Track.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | D4C2ADF31CF5C1AE00FE5851 /* Track.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Track.h; sourceTree = ""; }; 20 | D4C2ADF51CF5C1AE00FE5851 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 21 | D4C2ADFB1CF5C21200FE5851 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Cache.swift; path = ../../Track/Cache.swift; sourceTree = ""; }; 22 | D4C2ADFC1CF5C21200FE5851 /* DiskCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiskCache.swift; path = ../../Track/DiskCache.swift; sourceTree = ""; }; 23 | D4C2ADFD1CF5C21200FE5851 /* LinkedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LinkedList.swift; path = ../../Track/LinkedList.swift; sourceTree = ""; }; 24 | D4C2ADFE1CF5C21200FE5851 /* MemoryCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MemoryCache.swift; path = ../../Track/MemoryCache.swift; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | D4C2ADEC1CF5C1AE00FE5851 /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | D4C2ADE61CF5C1AE00FE5851 = { 39 | isa = PBXGroup; 40 | children = ( 41 | D4C2ADF21CF5C1AE00FE5851 /* Track */, 42 | D4C2ADF11CF5C1AE00FE5851 /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | D4C2ADF11CF5C1AE00FE5851 /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | D4C2ADF01CF5C1AE00FE5851 /* Track.framework */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | D4C2ADF21CF5C1AE00FE5851 /* Track */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | D4C2ADFB1CF5C21200FE5851 /* Cache.swift */, 58 | D4C2ADFC1CF5C21200FE5851 /* DiskCache.swift */, 59 | D4C2ADFD1CF5C21200FE5851 /* LinkedList.swift */, 60 | D4C2ADFE1CF5C21200FE5851 /* MemoryCache.swift */, 61 | D4C2ADF31CF5C1AE00FE5851 /* Track.h */, 62 | D4C2ADF51CF5C1AE00FE5851 /* Info.plist */, 63 | ); 64 | path = Track; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXHeadersBuildPhase section */ 70 | D4C2ADED1CF5C1AE00FE5851 /* Headers */ = { 71 | isa = PBXHeadersBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | D4C2ADF41CF5C1AE00FE5851 /* Track.h in Headers */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXHeadersBuildPhase section */ 79 | 80 | /* Begin PBXNativeTarget section */ 81 | D4C2ADEF1CF5C1AE00FE5851 /* Track */ = { 82 | isa = PBXNativeTarget; 83 | buildConfigurationList = D4C2ADF81CF5C1AE00FE5851 /* Build configuration list for PBXNativeTarget "Track" */; 84 | buildPhases = ( 85 | D4C2ADEB1CF5C1AE00FE5851 /* Sources */, 86 | D4C2ADEC1CF5C1AE00FE5851 /* Frameworks */, 87 | D4C2ADED1CF5C1AE00FE5851 /* Headers */, 88 | D4C2ADEE1CF5C1AE00FE5851 /* Resources */, 89 | ); 90 | buildRules = ( 91 | ); 92 | dependencies = ( 93 | ); 94 | name = Track; 95 | productName = Track; 96 | productReference = D4C2ADF01CF5C1AE00FE5851 /* Track.framework */; 97 | productType = "com.apple.product-type.framework"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | D4C2ADE71CF5C1AE00FE5851 /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | LastUpgradeCheck = 1020; 106 | ORGANIZATIONNAME = "马权"; 107 | TargetAttributes = { 108 | D4C2ADEF1CF5C1AE00FE5851 = { 109 | CreatedOnToolsVersion = 7.3; 110 | LastSwiftMigration = 0800; 111 | }; 112 | }; 113 | }; 114 | buildConfigurationList = D4C2ADEA1CF5C1AE00FE5851 /* Build configuration list for PBXProject "Track" */; 115 | compatibilityVersion = "Xcode 3.2"; 116 | developmentRegion = en; 117 | hasScannedForEncodings = 0; 118 | knownRegions = ( 119 | en, 120 | Base, 121 | ); 122 | mainGroup = D4C2ADE61CF5C1AE00FE5851; 123 | productRefGroup = D4C2ADF11CF5C1AE00FE5851 /* Products */; 124 | projectDirPath = ""; 125 | projectRoot = ""; 126 | targets = ( 127 | D4C2ADEF1CF5C1AE00FE5851 /* Track */, 128 | ); 129 | }; 130 | /* End PBXProject section */ 131 | 132 | /* Begin PBXResourcesBuildPhase section */ 133 | D4C2ADEE1CF5C1AE00FE5851 /* Resources */ = { 134 | isa = PBXResourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | ); 138 | runOnlyForDeploymentPostprocessing = 0; 139 | }; 140 | /* End PBXResourcesBuildPhase section */ 141 | 142 | /* Begin PBXSourcesBuildPhase section */ 143 | D4C2ADEB1CF5C1AE00FE5851 /* Sources */ = { 144 | isa = PBXSourcesBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | D4C2AE011CF5C21200FE5851 /* LinkedList.swift in Sources */, 148 | D4C2AE021CF5C21200FE5851 /* MemoryCache.swift in Sources */, 149 | D4C2ADFF1CF5C21200FE5851 /* Cache.swift in Sources */, 150 | D4C2AE001CF5C21200FE5851 /* DiskCache.swift in Sources */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXSourcesBuildPhase section */ 155 | 156 | /* Begin XCBuildConfiguration section */ 157 | D4C2ADF61CF5C1AE00FE5851 /* Debug */ = { 158 | isa = XCBuildConfiguration; 159 | buildSettings = { 160 | ALWAYS_SEARCH_USER_PATHS = NO; 161 | CLANG_ANALYZER_NONNULL = YES; 162 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 163 | CLANG_CXX_LIBRARY = "libc++"; 164 | CLANG_ENABLE_MODULES = YES; 165 | CLANG_ENABLE_OBJC_ARC = YES; 166 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 167 | CLANG_WARN_BOOL_CONVERSION = YES; 168 | CLANG_WARN_COMMA = YES; 169 | CLANG_WARN_CONSTANT_CONVERSION = YES; 170 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 171 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 172 | CLANG_WARN_EMPTY_BODY = YES; 173 | CLANG_WARN_ENUM_CONVERSION = YES; 174 | CLANG_WARN_INFINITE_RECURSION = YES; 175 | CLANG_WARN_INT_CONVERSION = YES; 176 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 177 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 178 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 179 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 180 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 181 | CLANG_WARN_STRICT_PROTOTYPES = YES; 182 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 183 | CLANG_WARN_UNREACHABLE_CODE = YES; 184 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 185 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 186 | COPY_PHASE_STRIP = NO; 187 | CURRENT_PROJECT_VERSION = 1; 188 | DEBUG_INFORMATION_FORMAT = dwarf; 189 | ENABLE_STRICT_OBJC_MSGSEND = YES; 190 | ENABLE_TESTABILITY = YES; 191 | GCC_C_LANGUAGE_STANDARD = gnu99; 192 | GCC_DYNAMIC_NO_PIC = NO; 193 | GCC_NO_COMMON_BLOCKS = YES; 194 | GCC_OPTIMIZATION_LEVEL = 0; 195 | GCC_PREPROCESSOR_DEFINITIONS = ( 196 | "DEBUG=1", 197 | "$(inherited)", 198 | ); 199 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 200 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 201 | GCC_WARN_UNDECLARED_SELECTOR = YES; 202 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 203 | GCC_WARN_UNUSED_FUNCTION = YES; 204 | GCC_WARN_UNUSED_VARIABLE = YES; 205 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 206 | MTL_ENABLE_DEBUG_INFO = YES; 207 | ONLY_ACTIVE_ARCH = YES; 208 | SDKROOT = iphoneos; 209 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 210 | TARGETED_DEVICE_FAMILY = "1,2"; 211 | VERSIONING_SYSTEM = "apple-generic"; 212 | VERSION_INFO_PREFIX = ""; 213 | }; 214 | name = Debug; 215 | }; 216 | D4C2ADF71CF5C1AE00FE5851 /* Release */ = { 217 | isa = XCBuildConfiguration; 218 | buildSettings = { 219 | ALWAYS_SEARCH_USER_PATHS = NO; 220 | CLANG_ANALYZER_NONNULL = YES; 221 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 222 | CLANG_CXX_LIBRARY = "libc++"; 223 | CLANG_ENABLE_MODULES = YES; 224 | CLANG_ENABLE_OBJC_ARC = YES; 225 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 226 | CLANG_WARN_BOOL_CONVERSION = YES; 227 | CLANG_WARN_COMMA = YES; 228 | CLANG_WARN_CONSTANT_CONVERSION = YES; 229 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 230 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 231 | CLANG_WARN_EMPTY_BODY = YES; 232 | CLANG_WARN_ENUM_CONVERSION = YES; 233 | CLANG_WARN_INFINITE_RECURSION = YES; 234 | CLANG_WARN_INT_CONVERSION = YES; 235 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 236 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 237 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 239 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 240 | CLANG_WARN_STRICT_PROTOTYPES = YES; 241 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 242 | CLANG_WARN_UNREACHABLE_CODE = YES; 243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 244 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 245 | COPY_PHASE_STRIP = NO; 246 | CURRENT_PROJECT_VERSION = 1; 247 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 248 | ENABLE_NS_ASSERTIONS = NO; 249 | ENABLE_STRICT_OBJC_MSGSEND = YES; 250 | GCC_C_LANGUAGE_STANDARD = gnu99; 251 | GCC_NO_COMMON_BLOCKS = YES; 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 259 | MTL_ENABLE_DEBUG_INFO = NO; 260 | SDKROOT = iphoneos; 261 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 262 | TARGETED_DEVICE_FAMILY = "1,2"; 263 | VALIDATE_PRODUCT = YES; 264 | VERSIONING_SYSTEM = "apple-generic"; 265 | VERSION_INFO_PREFIX = ""; 266 | }; 267 | name = Release; 268 | }; 269 | D4C2ADF91CF5C1AE00FE5851 /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | CLANG_ENABLE_MODULES = YES; 273 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 274 | DEFINES_MODULE = YES; 275 | DYLIB_COMPATIBILITY_VERSION = 1; 276 | DYLIB_CURRENT_VERSION = 1; 277 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 278 | INFOPLIST_FILE = Track/Info.plist; 279 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 280 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 281 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 282 | PRODUCT_BUNDLE_IDENTIFIER = maquan.Track; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SKIP_INSTALL = YES; 285 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 286 | SWIFT_VERSION = 5.0; 287 | }; 288 | name = Debug; 289 | }; 290 | D4C2ADFA1CF5C1AE00FE5851 /* Release */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | CLANG_ENABLE_MODULES = YES; 294 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 295 | DEFINES_MODULE = YES; 296 | DYLIB_COMPATIBILITY_VERSION = 1; 297 | DYLIB_CURRENT_VERSION = 1; 298 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 299 | INFOPLIST_FILE = Track/Info.plist; 300 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 301 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 302 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 303 | PRODUCT_BUNDLE_IDENTIFIER = maquan.Track; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | SKIP_INSTALL = YES; 306 | SWIFT_VERSION = 5.0; 307 | }; 308 | name = Release; 309 | }; 310 | /* End XCBuildConfiguration section */ 311 | 312 | /* Begin XCConfigurationList section */ 313 | D4C2ADEA1CF5C1AE00FE5851 /* Build configuration list for PBXProject "Track" */ = { 314 | isa = XCConfigurationList; 315 | buildConfigurations = ( 316 | D4C2ADF61CF5C1AE00FE5851 /* Debug */, 317 | D4C2ADF71CF5C1AE00FE5851 /* Release */, 318 | ); 319 | defaultConfigurationIsVisible = 0; 320 | defaultConfigurationName = Release; 321 | }; 322 | D4C2ADF81CF5C1AE00FE5851 /* Build configuration list for PBXNativeTarget "Track" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | D4C2ADF91CF5C1AE00FE5851 /* Debug */, 326 | D4C2ADFA1CF5C1AE00FE5851 /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | /* End XCConfigurationList section */ 332 | }; 333 | rootObject = D4C2ADE71CF5C1AE00FE5851 /* Project object */; 334 | } 335 | -------------------------------------------------------------------------------- /Framework/Track.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Framework/Track.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Framework/Track.xcodeproj/xcshareddata/xcschemes/Track.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Framework/Track/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.2.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Framework/Track/Track.h: -------------------------------------------------------------------------------- 1 | //The MIT License (MIT) 2 | // 3 | //Copyright (c) 2016 U Are My SunShine 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 | #import 24 | 25 | //! Project version number for Track. 26 | FOUNDATION_EXPORT double TrackVersionNumber; 27 | 28 | //! Project version string for Track. 29 | FOUNDATION_EXPORT const unsigned char TrackVersionString[]; 30 | 31 | // In this header, you should import all the public headers of your framework using statements like #import 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 U Are My SunShine 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 | -------------------------------------------------------------------------------- /Track.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Track' 3 | s.summary = 'Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU.' 4 | s.version = '3.0.0' 5 | s.license = { :type => 'MIT', :file => 'LICENSE' } 6 | s.authors = { 'maquannene' => 'maquannene@gmail.com' } 7 | s.homepage = 'https://github.com/maquannene/Track' 8 | s.platform = :ios, '8.0' 9 | s.ios.deployment_target = '8.0' 10 | s.source = { :git => 'https://github.com/maquannene/Track.git', :tag => s.version.to_s } 11 | s.source_files = 'Track/*.{swift}' 12 | s.frameworks = 'UIKit', 'QuartzCore' 13 | s.xcconfig = { 'SWIFT_VERSION' => '5.0' } 14 | end 15 | -------------------------------------------------------------------------------- /Track/Cache.swift: -------------------------------------------------------------------------------- 1 | //The MIT License (MIT) 2 | // 3 | //Copyright (c) 2016 U Are My SunShine 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 | import Foundation 24 | 25 | /** 26 | CacheGenerator, support `for...in` loops, it is thread safe. 27 | */ 28 | open class CacheGenerator : IteratorProtocol { 29 | 30 | public typealias Element = (String, AnyObject) 31 | 32 | fileprivate var _memoryCacheGenerator: MemoryCacheGenerator 33 | 34 | fileprivate var _diskCacheGenerator: DiskCacheGenerator 35 | 36 | fileprivate var _memoryCache: MemoryCache 37 | 38 | fileprivate let _semaphoreLock: DispatchSemaphore = DispatchSemaphore(value: 1) 39 | 40 | fileprivate init(memoryCacheGenerator: MemoryCacheGenerator, diskCacheGenerator: DiskCacheGenerator, memoryCache: MemoryCache) { 41 | self._memoryCacheGenerator = memoryCacheGenerator 42 | self._diskCacheGenerator = diskCacheGenerator 43 | self._memoryCache = memoryCache 44 | } 45 | 46 | /** 47 | Advance to the next element and return it, or `nil` if no next element exists. 48 | 49 | - returns: next element 50 | */ 51 | 52 | open func next() -> Element? { 53 | if let element = _memoryCacheGenerator.next() { 54 | self._diskCacheGenerator.shift() 55 | return element 56 | } 57 | else { 58 | if let element: Element = _diskCacheGenerator.next() as! CacheGenerator.Element? { 59 | _memoryCache._unsafeSet(object: element.1, forKey: element.0) 60 | return element 61 | } 62 | } 63 | return nil 64 | } 65 | } 66 | 67 | /** 68 | Cache async operation callback 69 | */ 70 | public typealias CacheAsyncCompletion = (_ cache: Cache?, _ key: String?, _ object: Any?) -> Void 71 | 72 | /** 73 | Track Cache Prefix, use on default disk cache folder name and queue name 74 | */ 75 | let TrackCachePrefix: String = "com.trackcache." 76 | 77 | /** 78 | Track Cache default name, default disk cache folder name 79 | */ 80 | let TrackCacheDefauleName: String = "defaultTrackCache" 81 | 82 | /** 83 | TrackCache is a thread safe cache, contain a thread safe memory cache and a thread safe diskcache. 84 | And support thread safe `for`...`in` loops, map, forEach... 85 | */ 86 | open class Cache { 87 | 88 | /** 89 | cache name, used to create disk cache folder 90 | */ 91 | public let name: String 92 | 93 | /** 94 | Thread safe memeory cache 95 | */ 96 | public let memoryCache: MemoryCache 97 | 98 | /** 99 | Thread safe disk cache 100 | */ 101 | public let diskCache: DiskCache 102 | 103 | fileprivate let _queue: DispatchQueue = DispatchQueue(label: TrackCachePrefix + (String(describing: Cache.self)), attributes: DispatchQueue.Attributes.concurrent) 104 | 105 | /** 106 | A share cache, contain a thread safe memory cache and a thread safe diskcache 107 | */ 108 | public static let shareInstance = Cache(name: TrackCacheDefauleName)! 109 | 110 | /** 111 | Design constructor 112 | The same name has the same diskCache, but different memorycache. 113 | 114 | - parameter name: cache name 115 | - parameter path: diskcache path 116 | */ 117 | public init?(name: String, path: String) { 118 | if name.count == 0 || path.count == 0 { 119 | return nil 120 | } 121 | self.diskCache = DiskCache(name: name, path: path)! 122 | self.name = name 123 | self.memoryCache = MemoryCache.shareInstance 124 | } 125 | 126 | /** 127 | Convenience constructor, use default path Library/Caches/ 128 | 129 | - parameter name: cache name 130 | */ 131 | public convenience init?(name: String){ 132 | self.init(name: name, path: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]) 133 | } 134 | } 135 | 136 | // MARK: 137 | // MARK: Public 138 | public extension Cache { 139 | // MARK: Async 140 | /** 141 | Async store an object for the unique key in the memory cache and disk cache 142 | completion will be call after object has been store in memory cache and disk cache 143 | 144 | - parameter object: object must be implement NSCoding protocal 145 | - parameter key: unique key 146 | - parameter completion: stroe completion call back 147 | */ 148 | func set(object: NSCoding, forKey key: String, completion: CacheAsyncCompletion?) { 149 | _queue.async { [weak self] in 150 | guard let strongSelf = self else { completion?(nil, key, object); return } 151 | strongSelf.set(object: object, forKey: key) 152 | completion?(strongSelf, key, object) 153 | } 154 | } 155 | 156 | /** 157 | Async search object according to unique key 158 | search from memory cache first, if not found, will search from diskCache 159 | 160 | - parameter key: object unique key 161 | - parameter completion: search completion call back 162 | */ 163 | func object(forKey key: String, completion: CacheAsyncCompletion?) { 164 | _queue.async { [weak self] in 165 | guard let strongSelf = self else { return } 166 | strongSelf.memoryCache.object(forKey: key) { [weak self] (memCache, memKey, memObject) in 167 | guard let strongSelf = self else { return } 168 | if memObject != nil { 169 | strongSelf._queue.async { [weak self] in 170 | completion?(self, memKey, memObject) 171 | } 172 | } 173 | else { 174 | strongSelf.diskCache.object(forKey: key) { [weak self] (diskCache, diskKey, diskObject) in 175 | guard let strongSelf = self else { return } 176 | if let diskKey = diskKey, let diskCache = diskCache { 177 | strongSelf.memoryCache.set(object: diskCache, forKey: diskKey, completion: nil) 178 | } 179 | strongSelf._queue.async { [weak self] in 180 | completion?(self, diskKey, diskObject) 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | /** 189 | Async remove object from memory cache and disk cache 190 | 191 | - parameter key: object unique key 192 | - parameter completion: remove completion call back 193 | */ 194 | func removeObject(forKey key: String, completion: CacheAsyncCompletion?) { 195 | _queue.async { [weak self] in 196 | guard let strongSelf = self else { completion?(nil, key, nil); return } 197 | strongSelf.removeObject(forKey: key) 198 | completion?(strongSelf, key, nil) 199 | } 200 | 201 | } 202 | 203 | /** 204 | Async remove all objects 205 | 206 | - parameter completion: remove completion call back 207 | */ 208 | func removeAllObjects(_ completion: CacheAsyncCompletion?) { 209 | _queue.async { [weak self] in 210 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 211 | strongSelf.removeAllObjects() 212 | completion?(strongSelf, nil, nil) 213 | } 214 | 215 | } 216 | 217 | // MARK: Sync 218 | /** 219 | Sync store an object for the unique key in the memory cache and disk cache 220 | 221 | - parameter object: object must be implement NSCoding protocal 222 | - parameter key: unique key 223 | - parameter completion: stroe completion call back 224 | */ 225 | func set(object: NSCoding, forKey key: String) { 226 | memoryCache.set(object: object, forKey: key) 227 | diskCache.set(object: object, forKey: key) 228 | } 229 | 230 | /** 231 | Sync search an object according to unique key 232 | search from memory cache first, if not found, will search from diskCache 233 | 234 | - parameter key: object unique key 235 | - parameter completion: search completion call back 236 | */ 237 | 238 | func object(forKey key: String) -> AnyObject? { 239 | if let object = memoryCache.object(forKey: key) { 240 | return object 241 | } 242 | else { 243 | if let object = diskCache.object(forKey: key) { 244 | memoryCache.set(object: object, forKey: key) 245 | return object 246 | } 247 | } 248 | return nil 249 | } 250 | 251 | /** 252 | Sync remove object from memory cache and disk cache 253 | 254 | - parameter key: object unique key 255 | */ 256 | func removeObject(forKey key: String) { 257 | memoryCache.removeObject(forKey: key) 258 | diskCache.removeObject(forKey: key) 259 | } 260 | 261 | /** 262 | Sync remove all objects 263 | */ 264 | func removeAllObjects() { 265 | memoryCache.removeAllObjects() 266 | diskCache.removeAllObjects() 267 | } 268 | 269 | /** 270 | subscript method, sync set and get 271 | 272 | - parameter key: object unique key 273 | */ 274 | subscript(key: String) -> NSCoding? { 275 | get { 276 | if let returnValue = object(forKey: key) as? NSCoding { 277 | return returnValue 278 | } 279 | return nil 280 | } 281 | set { 282 | if let newValue = newValue { 283 | set(object: newValue, forKey: key) 284 | } 285 | else { 286 | removeObject(forKey: key) 287 | } 288 | } 289 | } 290 | } 291 | 292 | // MARK: SequenceType 293 | extension Cache : Sequence { 294 | /** 295 | CacheGenerator 296 | */ 297 | public typealias Iterator = CacheGenerator 298 | 299 | /** 300 | Returns a generator over the elements of this sequence. 301 | It is thread safe, if you call `generate()`, remember release it, 302 | otherwise maybe it lead to deadlock. 303 | 304 | - returns: A generator 305 | */ 306 | 307 | public func makeIterator() -> CacheGenerator { 308 | var generatror: CacheGenerator 309 | generatror = CacheGenerator(memoryCacheGenerator: memoryCache.makeIterator(), diskCacheGenerator: diskCache.makeIterator(), memoryCache: memoryCache) 310 | return generatror 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /Track/DiskCache.swift: -------------------------------------------------------------------------------- 1 | //The MIT License (MIT) 2 | // 3 | //Copyright (c) 2016 U Are My SunShine 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 | /** 24 | DiskCache 25 | 26 | thread safe = concurrent + semaphore lock 27 | 28 | sync 29 | thread safe write = write + semaphore lock 30 | thread safe read = read + semaphore lokc 31 | 32 | async 33 | thread safe write = async concurrent queue + thread safe sync write 34 | thread safe read = async concurrent queue + thread safe sync read 35 | */ 36 | 37 | import Foundation 38 | import QuartzCore 39 | 40 | /** 41 | * FastGeneratorType, inherit GeneratorType and provide a method to shift offset. 42 | */ 43 | public protocol FastGeneratorType: IteratorProtocol { 44 | 45 | /** 46 | Shift like next, but there is no return value. 47 | If you just shift offset, it`s implementation should fast than `next()` 48 | */ 49 | func shift() 50 | } 51 | 52 | /** 53 | DiskCacheGenerator, support `for...in` `map` `forEach`..., it is thread safe. 54 | */ 55 | open class DiskCacheGenerator : FastGeneratorType { 56 | 57 | public typealias Element = (String, Any) 58 | 59 | fileprivate var _lruGenerator: LRUGenerator 60 | 61 | fileprivate var _diskCache: DiskCache 62 | 63 | fileprivate var _completion: (() -> Void)? 64 | 65 | fileprivate init(generate: LRUGenerator, diskCache: DiskCache, completion: (() -> Void)?) { 66 | self._lruGenerator = generate 67 | self._diskCache = diskCache 68 | self._completion = completion 69 | } 70 | 71 | /** 72 | Advance to the next element and return it, or `nil` if no next element exists. 73 | 74 | - returns: next element 75 | */ 76 | 77 | open func next() -> Element? { 78 | if let key = _lruGenerator.next()?.key { 79 | if let value = _diskCache._unsafeObject(forKey: key) { 80 | return (key, value) 81 | } 82 | } 83 | return nil 84 | } 85 | 86 | /** 87 | Shift like next, but there is no return value and shift fast. 88 | */ 89 | open func shift() { 90 | let _ = _lruGenerator.shift() 91 | } 92 | 93 | deinit { 94 | _completion?() 95 | } 96 | } 97 | 98 | private class DiskCacheObject: LRUObject { 99 | 100 | var key: String = "" 101 | var cost: UInt = 0 102 | var date: Date = Date() 103 | 104 | init (key: String, cost: UInt = 0, date: Date) { 105 | self.key = key 106 | self.cost = cost 107 | self.date = date 108 | } 109 | 110 | convenience init (key: String, cost: UInt = 0) { 111 | self.init(key: key, cost: cost, date: Date()) 112 | } 113 | } 114 | 115 | public typealias DiskCacheAsyncCompletion = (_ cache: DiskCache?, _ key: String?, _ object: Any?) -> Void 116 | 117 | /** 118 | DiskCache is a thread safe cache implement by dispatch_semaphore_t lock and DISPATCH_QUEUE_CONCURRENT 119 | Cache algorithms policy use LRU (Least Recently Used) implement by linked list. 120 | You can manage cache through functions to limit size, age of entries and memory usage to eliminate least recently used object. 121 | And support thread safe `for`...`in` loops, map, forEach... 122 | */ 123 | open class DiskCache { 124 | 125 | /** 126 | DiskCache folder name 127 | */ 128 | public let name: String 129 | 130 | /** 131 | DiskCache folder path URL 132 | */ 133 | public let cacheURL: URL 134 | 135 | /** 136 | Disk cache object total count 137 | */ 138 | open var totalCount: UInt { 139 | get { 140 | _lock() 141 | let count = _cache.count 142 | _unlock() 143 | return count 144 | } 145 | } 146 | 147 | /** 148 | Disk cache object total cost (byte) 149 | */ 150 | open var totalCost: UInt { 151 | get { 152 | _lock() 153 | let cost = _cache.cost 154 | _unlock() 155 | return cost 156 | } 157 | } 158 | 159 | fileprivate var _countLimit: UInt = UInt.max 160 | 161 | /** 162 | The maximum total quantity 163 | */ 164 | open var countLimit: UInt { 165 | set { 166 | _lock() 167 | _countLimit = newValue 168 | _unlock() 169 | trim(toCount: newValue) 170 | } 171 | get { 172 | _lock() 173 | let countLimit = _countLimit 174 | _unlock() 175 | return countLimit 176 | } 177 | } 178 | 179 | fileprivate var _costLimit: UInt = UInt.max 180 | 181 | /** 182 | The maximum disk cost limit 183 | */ 184 | open var costLimit: UInt { 185 | set { 186 | _lock() 187 | _costLimit = newValue 188 | _unlock() 189 | trim(toCost: newValue) 190 | } 191 | get { 192 | _lock() 193 | let costLimit = _costLimit 194 | _unlock() 195 | return costLimit 196 | } 197 | } 198 | 199 | fileprivate var _ageLimit: TimeInterval = Double.greatestFiniteMagnitude 200 | 201 | /** 202 | Disk cache object age limit 203 | */ 204 | open var ageLimit: TimeInterval { 205 | set { 206 | _lock() 207 | _ageLimit = newValue 208 | _unlock() 209 | trim(toAge: newValue) 210 | } 211 | get { 212 | _lock() 213 | let ageLimit = _ageLimit 214 | _unlock() 215 | return ageLimit 216 | } 217 | } 218 | 219 | fileprivate let _cache: LRU = LRU() 220 | 221 | fileprivate let _queue: DispatchQueue = DispatchQueue(label: TrackCachePrefix + (String(describing: DiskCache.self)), attributes: DispatchQueue.Attributes.concurrent) 222 | 223 | fileprivate let _semaphoreLock: DispatchSemaphore = DispatchSemaphore(value: 1) 224 | 225 | /** 226 | A share disk cache, name "defauleTrackCache" path "Library/Caches/" 227 | */ 228 | public static let shareInstance = DiskCache(name: TrackCacheDefauleName)! 229 | 230 | /** 231 | Design constructor 232 | The same name and path has the same disk folder Cache 233 | 234 | - parameter name: disk cache folder name 235 | - parameter path: disk cache folder path 236 | 237 | - returns: if no name or path will be fail 238 | */ 239 | public init?(name: String, path: String) { 240 | if name.count == 0 || path.count == 0 { 241 | return nil 242 | } 243 | self.name = name 244 | self.cacheURL = URL(fileURLWithPath: path).appendingPathComponent(TrackCachePrefix + name, isDirectory: false) 245 | 246 | _lock() 247 | _queue.async { 248 | _ = self._createCacheDir() 249 | _ = self._loadFilesInfo() 250 | self._unlock() 251 | } 252 | } 253 | 254 | /** 255 | convenience constructor 256 | 257 | - parameter name: disk cache foler name 258 | 259 | - returns: if no name will be fail 260 | */ 261 | public convenience init?(name: String) { 262 | self.init(name: name, path: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]) 263 | } 264 | } 265 | 266 | // MARK: 267 | // MARK: Public 268 | public extension DiskCache { 269 | // MARK: Async 270 | /** 271 | Async store an object for the unique key in disk cache and store object info to linked list head 272 | completion will be call after object has been store in disk 273 | 274 | - parameter object: object must be implement NSCoding protocal 275 | - parameter key: unique key 276 | - parameter completion: stroe completion call back 277 | */ 278 | func set(object: NSCoding, forKey key: String, completion: DiskCacheAsyncCompletion?) { 279 | _queue.async { [weak self] in 280 | guard let strongSelf = self else { completion?(nil, key, object); return } 281 | strongSelf.set(object: object, forKey: key) 282 | completion?(strongSelf, key, object) 283 | } 284 | } 285 | 286 | /** 287 | Async search object according to unique key 288 | if find object, object info will move to linked list head 289 | */ 290 | func object(forKey key: String, completion: DiskCacheAsyncCompletion?) { 291 | _queue.async { [weak self] in 292 | guard let strongSelf = self else { completion?(nil, key, nil); return } 293 | let object = strongSelf.object(forKey: key) 294 | completion?(strongSelf, key, object) 295 | } 296 | } 297 | 298 | /** 299 | Async remove object according to unique key from disk and remove object info from linked list 300 | */ 301 | func removeObject(forKey key: String, completion: DiskCacheAsyncCompletion?) { 302 | _queue.async { [weak self] in 303 | guard let strongSelf = self else { completion?(nil, key, nil); return } 304 | strongSelf.removeObject(forKey: key) 305 | completion?(strongSelf, key, nil) 306 | } 307 | } 308 | 309 | /** 310 | Async remove all object and info from disk and linked list 311 | */ 312 | func removeAllObjects(_ completion: DiskCacheAsyncCompletion?) { 313 | _queue.async { [weak self] in 314 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 315 | strongSelf.removeAllObjects() 316 | completion?(strongSelf, nil, nil) 317 | } 318 | } 319 | 320 | /** 321 | Async trim disk cache total to countLimit according LRU 322 | 323 | - parameter countLimit: maximum countLimit 324 | */ 325 | func trim(toCount countLimit: UInt, completion: DiskCacheAsyncCompletion?) { 326 | _queue.async { [weak self] in 327 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 328 | strongSelf.trim(toCount: countLimit) 329 | completion?(strongSelf, nil, nil) 330 | } 331 | } 332 | 333 | /** 334 | Async trim disk cache totalcost to costLimit according LRU 335 | 336 | - parameter costLimit: maximum costLimit 337 | */ 338 | func trim(toCost costLimit: UInt, completion: DiskCacheAsyncCompletion?) { 339 | _queue.async { [weak self] in 340 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 341 | strongSelf.trim(toCost: costLimit) 342 | completion?(strongSelf, nil, nil) 343 | } 344 | } 345 | 346 | /** 347 | Async trim disk cache objects which age greater than ageLimit 348 | 349 | - parameter ageLimit: maximum ageLimit 350 | */ 351 | func trim(toAge ageLimit: TimeInterval, completion: DiskCacheAsyncCompletion?) { 352 | _queue.async { [weak self] in 353 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 354 | strongSelf.trim(toAge: ageLimit) 355 | completion?(strongSelf, nil, nil) 356 | } 357 | } 358 | 359 | // MARK: Sync 360 | /** 361 | Sync store an object for the unique key in disk cache and store object info to linked list head 362 | */ 363 | func set(object: NSCoding, forKey key: String) { 364 | guard let fileURL = _generateFileURL(key, path: cacheURL) else { return } 365 | let filePath = fileURL.path 366 | _lock() 367 | if NSKeyedArchiver.archiveRootObject(object, toFile: filePath) == true { 368 | do { 369 | let date: Date = Date() 370 | try FileManager.default.setAttributes([FileAttributeKey.modificationDate : date], ofItemAtPath: filePath) 371 | let infosDic: [URLResourceKey : AnyObject] = try (fileURL as NSURL).resourceValues(forKeys: [URLResourceKey.totalFileAllocatedSizeKey]) as [URLResourceKey : AnyObject] 372 | var fileSize: UInt = 0 373 | if let fileSizeNumber = infosDic[URLResourceKey.totalFileAllocatedSizeKey] as? NSNumber { 374 | fileSize = fileSizeNumber.uintValue 375 | } 376 | _cache.set(object: DiskCacheObject(key: key, cost: fileSize, date: date), forKey: key) 377 | } catch {} 378 | } 379 | if _cache.cost > _costLimit { 380 | _unsafeTrim(toCost: _costLimit) 381 | } 382 | if _cache.count > _countLimit { 383 | _unsafeTrim(toCount: _countLimit) 384 | } 385 | _unlock() 386 | } 387 | 388 | /** 389 | Sync search object according to unique key 390 | if find object, object info will move to linked list head 391 | */ 392 | 393 | func object(forKey key: String) -> AnyObject? { 394 | _lock() 395 | let object = _unsafeObject(forKey: key) 396 | _unlock() 397 | return object 398 | } 399 | 400 | /** 401 | Sync remove object according to unique key from disk and remove object info from linked list 402 | */ 403 | func removeObject(forKey key: String) { 404 | guard let fileURL = _generateFileURL(key, path: cacheURL) else { return } 405 | let filePath = fileURL.path 406 | _lock() 407 | if FileManager.default.fileExists(atPath: filePath) { 408 | do { 409 | try FileManager.default.removeItem(atPath: filePath) 410 | _ = _cache.removeObject(forKey: key) 411 | } catch {} 412 | } 413 | _unlock() 414 | } 415 | 416 | /** 417 | Sync remove all object and info from disk and linked list 418 | */ 419 | func removeAllObjects() { 420 | _lock() 421 | if FileManager.default.fileExists(atPath: self.cacheURL.path) { 422 | do { 423 | try FileManager.default.removeItem(atPath: self.cacheURL.path) 424 | _cache.removeAllObjects() 425 | } catch {} 426 | } 427 | _unlock() 428 | } 429 | 430 | /** 431 | Async trim disk cache total to countLimit according LRU 432 | */ 433 | func trim(toCount countLimit: UInt) { 434 | if self.totalCount <= countLimit { 435 | return 436 | } 437 | if countLimit == 0 { 438 | removeAllObjects() 439 | return 440 | } 441 | _lock() 442 | _unsafeTrim(toCount: countLimit) 443 | _unlock() 444 | } 445 | 446 | /** 447 | Sync trim disk cache totalcost to costLimit according LRU 448 | */ 449 | func trim(toCost costLimit: UInt) { 450 | if self.totalCost <= costLimit { 451 | return 452 | } 453 | if costLimit == 0 { 454 | removeAllObjects() 455 | return 456 | } 457 | _lock() 458 | _unsafeTrim(toCost: costLimit) 459 | _unlock() 460 | } 461 | 462 | /** 463 | Sync trim disk cache objects which age greater than ageLimit 464 | */ 465 | func trim(toAge ageLimit: TimeInterval) { 466 | if ageLimit <= 0 { 467 | removeAllObjects() 468 | return 469 | } 470 | _lock() 471 | _unsafeTrim(toAge: ageLimit) 472 | _unlock() 473 | } 474 | 475 | subscript(key: String) -> NSCoding? { 476 | get { 477 | if let returnValue = object(forKey: key) as? NSCoding { 478 | return returnValue 479 | } 480 | return nil 481 | } 482 | set { 483 | if let newValue = newValue { 484 | set(object: newValue, forKey: key) 485 | } 486 | else { 487 | removeObject(forKey: key) 488 | } 489 | } 490 | } 491 | } 492 | 493 | // MARK: SequenceType 494 | extension DiskCache : Sequence { 495 | /** 496 | MemoryCacheGenerator 497 | */ 498 | public typealias Iterator = DiskCacheGenerator 499 | 500 | /** 501 | Returns a generator over the elements of this sequence. 502 | It is thread safe, if you call `generate()`, remember release it, 503 | otherwise maybe it lead to deadlock. 504 | 505 | - returns: A generator 506 | */ 507 | 508 | public func makeIterator() -> DiskCacheGenerator { 509 | var generatror: DiskCacheGenerator 510 | _lock() 511 | generatror = DiskCacheGenerator(generate: _cache.makeIterator(), diskCache: self) { 512 | self._unlock() 513 | } 514 | return generatror 515 | } 516 | } 517 | 518 | // MARK: 519 | // MARK: Private 520 | private extension DiskCache { 521 | 522 | func _createCacheDir() -> Bool { 523 | if FileManager.default.fileExists(atPath: cacheURL.path) { 524 | return false 525 | } 526 | do { 527 | try FileManager.default.createDirectory(atPath: cacheURL.path, withIntermediateDirectories: true, attributes: nil) 528 | } catch { 529 | return false 530 | } 531 | return true 532 | } 533 | 534 | func _loadFilesInfo() -> Bool { 535 | var fileInfos: [DiskCacheObject] = [DiskCacheObject]() 536 | let fileInfoKeys: [URLResourceKey] = [URLResourceKey.contentModificationDateKey, URLResourceKey.totalFileAllocatedSizeKey] 537 | do { 538 | let filesURL: [URL] = try FileManager.default.contentsOfDirectory(at: cacheURL, includingPropertiesForKeys: fileInfoKeys, options: .skipsHiddenFiles) 539 | for fileURL: URL in filesURL { 540 | do { 541 | let infosDic: [URLResourceKey : AnyObject] = try (fileURL as NSURL).resourceValues(forKeys: fileInfoKeys) as [URLResourceKey : AnyObject] 542 | 543 | if let key = fileURL.lastPathComponent as String?, 544 | let date = infosDic[URLResourceKey.contentModificationDateKey] as? Date, 545 | let fileSize = infosDic[URLResourceKey.totalFileAllocatedSizeKey] as? NSNumber { 546 | fileInfos.append(DiskCacheObject(key: key, cost: fileSize.uintValue, date: date)) 547 | } 548 | } 549 | catch { 550 | return false 551 | } 552 | } 553 | fileInfos.sort { $0.date.timeIntervalSince1970 < $1.date.timeIntervalSince1970 } 554 | fileInfos.forEach { 555 | _cache.set(object: $0, forKey: $0.key) 556 | } 557 | } catch { 558 | return false 559 | } 560 | return true 561 | } 562 | 563 | func _unsafeTrim(toCount countLimit: UInt) { 564 | if var lastObject: DiskCacheObject = _cache.lastObject() { 565 | while (_cache.count > countLimit) { 566 | if let fileURL = _generateFileURL(lastObject.key, path: cacheURL), FileManager.default.fileExists(atPath: fileURL.path) { 567 | do { 568 | try FileManager.default.removeItem(atPath: fileURL.path) 569 | _cache.removeLastObject() 570 | guard let newLastObject = _cache.lastObject() else { break } 571 | lastObject = newLastObject 572 | } catch {} 573 | } 574 | } 575 | } 576 | } 577 | 578 | func _unsafeTrim(toCost costLimit: UInt) { 579 | if var lastObject: DiskCacheObject = _cache.lastObject() { 580 | while (_cache.cost > costLimit) { 581 | if let fileURL = _generateFileURL(lastObject.key, path: cacheURL) , FileManager.default.fileExists(atPath: fileURL.path) { 582 | do { 583 | try FileManager.default.removeItem(atPath: fileURL.path) 584 | _cache.removeLastObject() 585 | guard let newLastObject = _cache.lastObject() else { break } 586 | lastObject = newLastObject 587 | } catch {} 588 | } 589 | } 590 | } 591 | } 592 | 593 | func _unsafeTrim(toAge ageLimit: TimeInterval) { 594 | if var lastObject: DiskCacheObject = _cache.lastObject() { 595 | while (lastObject.date.timeIntervalSince1970 < Date().timeIntervalSince1970 - ageLimit) { 596 | if let fileURL = _generateFileURL(lastObject.key, path: cacheURL) , FileManager.default.fileExists(atPath: fileURL.path) { 597 | do { 598 | try FileManager.default.removeItem(atPath: fileURL.path) 599 | _cache.removeLastObject() 600 | guard let newLastObject = _cache.lastObject() else { break } 601 | lastObject = newLastObject 602 | } catch {} 603 | } 604 | } 605 | } 606 | } 607 | 608 | func _unsafeObject(forKey key: String) -> AnyObject? { 609 | guard let fileURL = _generateFileURL(key, path: cacheURL) else { return nil } 610 | var object: AnyObject? = nil 611 | let date: Date = Date() 612 | if FileManager.default.fileExists(atPath: fileURL.path) { 613 | object = NSKeyedUnarchiver.unarchiveObject(withFile: fileURL.path) as AnyObject? 614 | do { 615 | try FileManager.default.setAttributes([FileAttributeKey.modificationDate : date], ofItemAtPath: fileURL.path) 616 | if object != nil { 617 | if let diskCacheObj = _cache.object(forKey: key) { 618 | diskCacheObj.date = date 619 | } 620 | } 621 | } catch { 622 | 623 | } 624 | } 625 | return object 626 | } 627 | 628 | func _generateFileURL(_ key: String, path: URL) -> URL? { 629 | return path.appendingPathComponent(key) 630 | } 631 | 632 | func _lock() { 633 | _ = _semaphoreLock.wait(timeout: DispatchTime.distantFuture) 634 | } 635 | 636 | func _unlock() { 637 | _semaphoreLock.signal() 638 | } 639 | } 640 | -------------------------------------------------------------------------------- /Track/LinkedList.swift: -------------------------------------------------------------------------------- 1 | //The MIT License (MIT) 2 | // 3 | //Copyright (c) 2016 U Are My SunShine 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 | import Foundation 24 | import QuartzCore 25 | 26 | protocol LRUObject { 27 | 28 | var key: String { get } 29 | var cost: UInt { get set } 30 | } 31 | 32 | class LRUGenerator : FastGeneratorType { 33 | 34 | typealias Element = T 35 | 36 | fileprivate let linkedListGenerator: LinkedListGenerator 37 | 38 | fileprivate let lru: LRU 39 | 40 | fileprivate init(linkedListGenerator: LinkedListGenerator, lru: LRU) { 41 | self.linkedListGenerator = linkedListGenerator 42 | self.lru = lru 43 | } 44 | 45 | 46 | func next() -> Element? { 47 | if let node = linkedListGenerator.next() { 48 | lru._linkedList.bringNodeToHead(node) 49 | return node.data 50 | } 51 | return nil 52 | } 53 | 54 | func shift() { 55 | if let node = linkedListGenerator.next() { 56 | lru._linkedList.bringNodeToHead(node) 57 | } 58 | } 59 | } 60 | 61 | class LRU { 62 | 63 | fileprivate typealias NodeType = Node 64 | 65 | var count: UInt { 66 | return _linkedList.count 67 | } 68 | 69 | fileprivate(set) var cost: UInt = 0 70 | 71 | fileprivate var _dic: NSMutableDictionary = NSMutableDictionary() 72 | 73 | fileprivate let _linkedList: LinkedList = LinkedList() 74 | 75 | func set(object: T, forKey key: String) { 76 | if let node: NodeType = _dic.object(forKey: key) as? NodeType { 77 | cost -= node.data.cost 78 | cost += object.cost 79 | node.data = object 80 | _linkedList.bringNodeToHead(node) 81 | } 82 | else { 83 | let node: NodeType = Node(data: object) 84 | cost += object.cost 85 | _dic.setObject(node, forKey: key as NSCopying) 86 | _linkedList.insertNode(node, atIndex: 0) 87 | } 88 | } 89 | 90 | func object(forKey key: String) -> T? { 91 | if let node: NodeType = _dic.object(forKey: key) as? NodeType { 92 | _linkedList.bringNodeToHead(node) 93 | return node.data 94 | } 95 | return nil 96 | } 97 | 98 | func allObjects() -> [T] { 99 | var objects: [T] = [T]() 100 | var indexNode: NodeType? = _linkedList.headNode 101 | while (true) { 102 | if let node: NodeType = indexNode { 103 | objects.append(node.data) 104 | indexNode = node.nextNode 105 | } 106 | else { 107 | break 108 | } 109 | } 110 | return objects 111 | } 112 | 113 | func removeObject(forKey key: String) -> T? { 114 | if let node: NodeType = _dic.object(forKey: key) as? NodeType { 115 | _dic.removeObject(forKey: key) 116 | _linkedList.removeNode(node) 117 | cost -= node.data.cost 118 | return node.data 119 | } 120 | return nil 121 | } 122 | 123 | func removeAllObjects() { 124 | _dic = NSMutableDictionary() 125 | _linkedList.removeAllNodes() 126 | cost = 0 127 | } 128 | 129 | func removeLastObject() { 130 | if let lastNode: NodeType = _linkedList.tailNode as NodeType? { 131 | _dic.removeObject(forKey: lastNode.data.key) 132 | _linkedList.removeNode(lastNode) 133 | cost -= lastNode.data.cost 134 | return 135 | } 136 | } 137 | 138 | func firstObject() -> T? { 139 | return _linkedList.headNode?.data 140 | } 141 | 142 | func lastObject() -> T? { 143 | return _linkedList.tailNode?.data 144 | } 145 | 146 | subscript(key: String) -> T? { 147 | get { 148 | return object(forKey: key) 149 | } 150 | set { 151 | if let newValue = newValue { 152 | set(object: newValue, forKey: key) 153 | } else { 154 | _ = removeObject(forKey: key) 155 | } 156 | } 157 | } 158 | } 159 | 160 | extension LRU : Sequence { 161 | 162 | typealias Iterator = LRUGenerator 163 | 164 | 165 | func makeIterator() -> LRUGenerator { 166 | var generatror: LRUGenerator 167 | generatror = LRUGenerator(linkedListGenerator: _linkedList.makeIterator(), lru: self) 168 | return generatror 169 | } 170 | } 171 | 172 | private class Node { 173 | 174 | weak var preNode: Node? 175 | weak var nextNode: Node? 176 | var data: T 177 | 178 | init(data: T) { 179 | self.data = data 180 | } 181 | } 182 | 183 | private class LinkedListGenerator : FastGeneratorType { 184 | 185 | typealias Element = Node 186 | 187 | var node: Node? 188 | 189 | init(node: Node?) { 190 | self.node = node 191 | } 192 | 193 | 194 | func next() -> Element? { 195 | if let node: Element = self.node { 196 | self.node = node.nextNode 197 | return node 198 | } 199 | else { 200 | return nil 201 | } 202 | } 203 | 204 | func shift() { 205 | self.node = self.node?.nextNode 206 | } 207 | } 208 | 209 | private class LinkedList { 210 | 211 | var count: UInt = 0 212 | weak var headNode: Node? 213 | weak var tailNode: Node? 214 | 215 | init() { 216 | 217 | } 218 | 219 | func insertNode(_ node: Node, atIndex index: UInt) { 220 | if index > count { 221 | return 222 | } 223 | node.preNode = nil 224 | node.nextNode = nil 225 | if count == 0 { 226 | headNode = node 227 | tailNode = node 228 | } 229 | else { 230 | if index == 0 { 231 | node.nextNode = headNode 232 | headNode?.preNode = node 233 | headNode = node 234 | } 235 | else if index == count { 236 | node.preNode = tailNode 237 | tailNode?.nextNode = node 238 | tailNode = node 239 | } 240 | else { 241 | let preNode = findNode(atIndex: index - 1) 242 | node.nextNode = preNode?.nextNode 243 | node.preNode = preNode 244 | node.nextNode?.preNode = node 245 | preNode?.nextNode = node 246 | } 247 | } 248 | count += 1 249 | } 250 | 251 | func bringNodeToHead(_ node: Node) { 252 | if node === headNode { 253 | return 254 | } 255 | if node === tailNode { 256 | tailNode = node.preNode 257 | tailNode?.nextNode = nil 258 | } 259 | else { 260 | node.nextNode?.preNode = node.preNode 261 | node.preNode?.nextNode = node.nextNode 262 | } 263 | 264 | node.preNode = nil 265 | node.nextNode = headNode 266 | headNode?.preNode = node 267 | headNode = node 268 | } 269 | 270 | func removeNode(_ node: Node) { 271 | if count == 0 { 272 | return 273 | } 274 | if node === headNode { 275 | headNode = node.nextNode 276 | headNode?.preNode = nil 277 | } 278 | else if node === tailNode { 279 | tailNode = node.preNode 280 | tailNode?.nextNode = nil 281 | } 282 | else { 283 | node.preNode?.nextNode = node.nextNode 284 | node.nextNode?.preNode = node.preNode 285 | } 286 | count -= 1 287 | } 288 | 289 | func findNode(atIndex index: UInt) -> Node? { 290 | if count == 0 { 291 | return nil 292 | } 293 | var node: Node? 294 | if index < count / 2 { 295 | node = headNode 296 | for _ in 1 ... index { 297 | node = node?.nextNode 298 | } 299 | } 300 | else { 301 | node = tailNode 302 | for _ in 1 ... count - (index - 1) { 303 | node = node?.preNode 304 | } 305 | } 306 | return node 307 | } 308 | 309 | func removeAllNodes() { 310 | headNode = nil 311 | tailNode = nil 312 | count = 0 313 | } 314 | } 315 | 316 | extension LinkedList : Sequence { 317 | 318 | fileprivate typealias Iterator = LinkedListGenerator 319 | 320 | 321 | fileprivate func makeIterator() -> LinkedListGenerator { 322 | var generatror: LinkedListGenerator 323 | generatror = LinkedListGenerator(node: headNode) 324 | return generatror 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /Track/MemoryCache.swift: -------------------------------------------------------------------------------- 1 | //The MIT License (MIT) 2 | // 3 | //Copyright (c) 2016 U Are My SunShine 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 | /** 24 | MemoryCache 25 | 26 | thread safe = concurrent + semaphore lock 27 | 28 | sync 29 | thread safe write = write + semaphore lock 30 | thread safe read = read + semaphore lokc 31 | 32 | async 33 | thread safe write = async concurrent queue + thread safe sync write 34 | thread safe read = async concurrent queue + thread safe sync read 35 | 36 | */ 37 | 38 | import Foundation 39 | import UIKit 40 | 41 | /** 42 | MemoryCacheGenerator, support `for...in` `map` `forEach`..., it is thread safe. 43 | */ 44 | open class MemoryCacheGenerator : IteratorProtocol { 45 | 46 | public typealias Element = (String, AnyObject) 47 | 48 | fileprivate var _lruGenerator: LRUGenerator 49 | 50 | fileprivate var _completion: (() -> Void)? 51 | 52 | fileprivate init(generate: LRUGenerator, cache: MemoryCache, completion: (() -> Void)?) { 53 | self._lruGenerator = generate 54 | self._completion = completion 55 | } 56 | 57 | /** 58 | Advance to the next element and return it, or `nil` if no next element exists. 59 | 60 | - returns: next element 61 | */ 62 | 63 | open func next() -> Element? { 64 | if let object = _lruGenerator.next() { 65 | return (object.key, object.value) 66 | } 67 | return nil 68 | } 69 | 70 | deinit { 71 | _completion?() 72 | } 73 | } 74 | 75 | private class MemoryCacheObject: LRUObject { 76 | 77 | var key: String = "" 78 | var cost: UInt = 0 79 | var time: TimeInterval = CACurrentMediaTime() 80 | var value: AnyObject 81 | 82 | init(key: String, value: AnyObject, cost: UInt = 0) { 83 | self.key = key 84 | self.value = value 85 | self.cost = cost 86 | } 87 | } 88 | 89 | public typealias MemoryCacheAsyncCompletion = (_ cache: MemoryCache?, _ key: String?, _ object: AnyObject?) -> Void 90 | 91 | /** 92 | MemoryCache is a thread safe cache implement by dispatch_semaphore_t lock and DISPATCH_QUEUE_CONCURRENT. 93 | Cache algorithms policy use LRU (Least Recently Used), implement by linked list and cache in NSDictionary. 94 | You can manage cache through functions to limit size, age of entries and memory usage to eliminate least recently used object. 95 | And support thread safe `for`...`in` loops, map, forEach... 96 | */ 97 | open class MemoryCache { 98 | 99 | /** 100 | Memory cache object total count 101 | */ 102 | open var totalCount: UInt { 103 | get { 104 | _lock() 105 | let count = _cache.count 106 | _unlock() 107 | return count 108 | } 109 | } 110 | 111 | /** 112 | Memory cache object total cost, if not set cost when set object, total cost may be zero 113 | */ 114 | open var totalCost: UInt { 115 | get { 116 | _lock() 117 | let cost = _cache.cost 118 | _unlock() 119 | return cost 120 | } 121 | } 122 | 123 | fileprivate var _countLimit: UInt = UInt.max 124 | 125 | /** 126 | The maximum total count limit 127 | */ 128 | open var countLimit: UInt { 129 | set { 130 | _lock() 131 | _countLimit = newValue 132 | _unsafeTrim(toCount: newValue) 133 | _unlock() 134 | } 135 | get { 136 | _lock() 137 | let countLimit = _countLimit 138 | _unlock() 139 | return countLimit 140 | } 141 | } 142 | 143 | fileprivate var _costLimit: UInt = UInt.max 144 | 145 | /** 146 | The maximum memory cost limit 147 | */ 148 | open var costLimit: UInt { 149 | set { 150 | _lock() 151 | _costLimit = newValue 152 | _unsafeTrim(toCost: newValue) 153 | _unlock() 154 | } 155 | get { 156 | _lock() 157 | let costLimit = _costLimit 158 | _unlock() 159 | return costLimit 160 | } 161 | } 162 | 163 | fileprivate var _ageLimit: TimeInterval = Double.greatestFiniteMagnitude 164 | 165 | /** 166 | Memory cache object age limit 167 | */ 168 | open var ageLimit: TimeInterval { 169 | set { 170 | _lock() 171 | _ageLimit = newValue 172 | _unsafeTrim(toAge: newValue) 173 | _unlock() 174 | } 175 | get { 176 | _lock() 177 | let ageLimit = _ageLimit 178 | _unlock() 179 | return ageLimit 180 | } 181 | } 182 | 183 | fileprivate var _autoRemoveAllObjectWhenMemoryWarning: Bool = true 184 | 185 | /** 186 | Auto remove all object when memory warning 187 | */ 188 | open var autoRemoveAllObjectWhenMemoryWarning: Bool { 189 | set { 190 | _lock() 191 | _autoRemoveAllObjectWhenMemoryWarning = newValue 192 | _unlock() 193 | } 194 | get { 195 | _lock() 196 | let autoRemoveAllObjectWhenMemoryWarning = _autoRemoveAllObjectWhenMemoryWarning 197 | _unlock() 198 | return autoRemoveAllObjectWhenMemoryWarning 199 | } 200 | } 201 | 202 | fileprivate var _autoRemoveAllObjectWhenEnterBackground = false 203 | 204 | /** 205 | Auto remove all object when enter background 206 | */ 207 | open var autoRemoveAllObjectWhenEnterBackground: Bool { 208 | set { 209 | _lock() 210 | _autoRemoveAllObjectWhenEnterBackground = newValue 211 | _unlock() 212 | } 213 | get { 214 | _lock() 215 | let autoRemoveAllObjectWhenEnterBackground = _autoRemoveAllObjectWhenEnterBackground 216 | _unlock() 217 | return autoRemoveAllObjectWhenEnterBackground 218 | } 219 | } 220 | 221 | fileprivate let _cache: LRU = LRU() 222 | 223 | fileprivate let _queue: DispatchQueue = DispatchQueue(label: TrackCachePrefix + String(describing: MemoryCache.self), attributes: DispatchQueue.Attributes.concurrent) 224 | 225 | fileprivate let _semaphoreLock: DispatchSemaphore = DispatchSemaphore(value: 1) 226 | 227 | /** 228 | A share memory cache 229 | */ 230 | public static let shareInstance = MemoryCache() 231 | 232 | /** 233 | Design constructor 234 | */ 235 | public init () { 236 | NotificationCenter.default.addObserver(self, selector: #selector(MemoryCache._didReceiveMemoryWarningNotification), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) 237 | NotificationCenter.default.addObserver(self, selector: #selector(MemoryCache._didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil) 238 | } 239 | } 240 | 241 | // MARK: 242 | // MARK: Public 243 | public extension MemoryCache { 244 | 245 | // MARK: Async 246 | /** 247 | Async store an object for the unique key in memory cache and add object to linked list head 248 | completion will be call after object has been store in memory 249 | 250 | - parameter object: object 251 | - parameter key: unique key 252 | - parameter completion: stroe completion call back 253 | */ 254 | func set(object: AnyObject, forKey key: String, cost: UInt = 0, completion: MemoryCacheAsyncCompletion?) { 255 | _queue.async { [weak self] in 256 | guard let strongSelf = self else { completion?(nil, key, object); return } 257 | strongSelf.set(object: object, forKey: key, cost: cost) 258 | completion?(strongSelf, key, object) 259 | } 260 | } 261 | 262 | /** 263 | Async search object according to unique key 264 | if find object, object will move to linked list head 265 | */ 266 | func object(forKey key: String, completion: MemoryCacheAsyncCompletion?) { 267 | _queue.async { [weak self] in 268 | guard let strongSelf = self else { completion?(nil, key, nil); return } 269 | let object = strongSelf.object(forKey: key) 270 | completion?(strongSelf, key, object) 271 | } 272 | } 273 | 274 | /** 275 | Async remove object according to unique key from cache dic and linked list 276 | */ 277 | func removeObject(forKey key: String, completion: MemoryCacheAsyncCompletion?) { 278 | _queue.async { [weak self] in 279 | guard let strongSelf = self else { completion?(nil, key, nil); return } 280 | strongSelf.removeObject(forKey: key) 281 | completion?(strongSelf, key, nil) 282 | } 283 | } 284 | 285 | /** 286 | Async remove all object and info from cache dic and clean linked list 287 | */ 288 | func removeAllObjects(_ completion: MemoryCacheAsyncCompletion?) { 289 | _queue.async { [weak self] in 290 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 291 | strongSelf.removeAllObjects() 292 | completion?(strongSelf, nil, nil) 293 | } 294 | } 295 | 296 | /** 297 | Async trim memory cache total to countLimit according LRU 298 | 299 | - parameter countLimit: maximum countLimit 300 | */ 301 | func trim(toCount countLimit: UInt, completion: MemoryCacheAsyncCompletion?) { 302 | _queue.async { [weak self] in 303 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 304 | strongSelf.trim(toCount: countLimit) 305 | completion?(strongSelf, nil, nil) 306 | } 307 | } 308 | 309 | /** 310 | Async trim memory cache totalcost to costLimit according LRU 311 | 312 | - parameter costLimit: maximum costLimit 313 | */ 314 | func trim(toCost costLimit: UInt, completion: MemoryCacheAsyncCompletion?) { 315 | _queue.async { [weak self] in 316 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 317 | strongSelf.trim(toCost: costLimit) 318 | completion?(strongSelf, nil, nil) 319 | } 320 | } 321 | 322 | /** 323 | Async trim memory cache objects which age greater than ageLimit 324 | 325 | - parameter ageLimit: maximum ageLimit 326 | */ 327 | func trim(toAge ageLimit: TimeInterval, completion: MemoryCacheAsyncCompletion?) { 328 | _queue.async { [weak self] in 329 | guard let strongSelf = self else { completion?(nil, nil, nil); return } 330 | strongSelf.trim(toAge: ageLimit) 331 | completion?(strongSelf, nil, nil) 332 | } 333 | } 334 | 335 | // MARK: Sync 336 | /** 337 | Sync store an object for the unique key in memory cache and add object to linked list head 338 | */ 339 | func set(object: AnyObject, forKey key: String, cost: UInt = 0) { 340 | _lock() 341 | _unsafeSet(object: object, forKey: key, cost: cost) 342 | _unlock() 343 | } 344 | 345 | /** 346 | Async search object according to unique key 347 | if find object, object will move to linked list head 348 | */ 349 | 350 | func object(forKey key: String) -> AnyObject? { 351 | var object: AnyObject? = nil 352 | _lock() 353 | let memoryObject: MemoryCacheObject? = _cache.object(forKey: key) 354 | memoryObject?.time = CACurrentMediaTime() 355 | object = memoryObject?.value 356 | _unlock() 357 | return object 358 | } 359 | 360 | /** 361 | Sync remove object according to unique key from cache dic and linked list 362 | */ 363 | func removeObject(forKey key: String) { 364 | _lock() 365 | _ = _cache.removeObject(forKey:key) 366 | _unlock() 367 | } 368 | 369 | /** 370 | Sync remove all object and info from cache dic and clean linked list 371 | */ 372 | func removeAllObjects() { 373 | _lock() 374 | _cache.removeAllObjects() 375 | _unlock() 376 | } 377 | 378 | /** 379 | Sync trim memory cache totalcost to costLimit according LRU 380 | */ 381 | func trim(toCount countLimit: UInt) { 382 | _lock() 383 | _unsafeTrim(toCount: countLimit) 384 | _unlock() 385 | } 386 | 387 | /** 388 | Sync trim memory cache totalcost to costLimit according LRU 389 | */ 390 | func trim(toCost costLimit: UInt) { 391 | _lock() 392 | _unsafeTrim(toCost: costLimit) 393 | _unlock() 394 | } 395 | 396 | /** 397 | Sync trim memory cache objects which age greater than ageLimit 398 | */ 399 | func trim(toAge ageLimit: TimeInterval) { 400 | _lock() 401 | _unsafeTrim(toAge: ageLimit) 402 | _unlock() 403 | } 404 | 405 | /** 406 | subscript method, sync set and get 407 | 408 | - parameter key: object unique key 409 | */ 410 | subscript(key: String) -> AnyObject? { 411 | get { 412 | return object(forKey: key) 413 | } 414 | set { 415 | if let newValue = newValue { 416 | set(object: newValue, forKey: key) 417 | } else { 418 | removeObject(forKey: key) 419 | } 420 | } 421 | } 422 | } 423 | 424 | // MARK: SequenceType 425 | extension MemoryCache : Sequence { 426 | /** 427 | MemoryCacheGenerator 428 | */ 429 | public typealias Iterator = MemoryCacheGenerator 430 | 431 | /** 432 | Returns a generator over the elements of this sequence. 433 | It is thread safe, if you call `generate()`, remember release it, 434 | otherwise maybe it lead to deadlock. 435 | 436 | - returns: A generator 437 | */ 438 | 439 | public func makeIterator() -> MemoryCacheGenerator { 440 | var generatror: MemoryCacheGenerator 441 | _lock() 442 | generatror = MemoryCacheGenerator(generate: _cache.makeIterator(), cache: self) { 443 | self._unlock() 444 | } 445 | return generatror 446 | } 447 | } 448 | 449 | // MARK: 450 | // MARK: Private 451 | extension MemoryCache { 452 | 453 | @objc fileprivate func _didReceiveMemoryWarningNotification() { 454 | if self.autoRemoveAllObjectWhenMemoryWarning { 455 | removeAllObjects(nil) 456 | } 457 | } 458 | 459 | @objc fileprivate func _didEnterBackgroundNotification() { 460 | if self.autoRemoveAllObjectWhenEnterBackground { 461 | removeAllObjects(nil) 462 | } 463 | } 464 | 465 | fileprivate func _unsafeTrim(toCount countLimit: UInt) { 466 | if _cache.count <= countLimit { 467 | return 468 | } 469 | if countLimit == 0 { 470 | _cache.removeAllObjects() 471 | return 472 | } 473 | if let _: MemoryCacheObject = _cache.lastObject() { 474 | while (_cache.count > countLimit) { 475 | _cache.removeLastObject() 476 | guard let _: MemoryCacheObject = _cache.lastObject() else { break } 477 | } 478 | } 479 | } 480 | 481 | fileprivate func _unsafeTrim(toCost costLimit: UInt) { 482 | if _cache.cost <= costLimit { 483 | return 484 | } 485 | if costLimit == 0 { 486 | _cache.removeAllObjects() 487 | return 488 | } 489 | if let _: MemoryCacheObject = _cache.lastObject() { 490 | while (_cache.cost > costLimit) { 491 | _cache.removeLastObject() 492 | guard let _: MemoryCacheObject = _cache.lastObject() else { break } 493 | } 494 | } 495 | } 496 | 497 | fileprivate func _unsafeTrim(toAge ageLimit: TimeInterval) { 498 | if ageLimit <= 0 { 499 | _cache.removeAllObjects() 500 | return 501 | } 502 | if var lastObject: MemoryCacheObject = _cache.lastObject() { 503 | while (CACurrentMediaTime() - lastObject.time > ageLimit) { 504 | _cache.removeLastObject() 505 | guard let newLastObject: MemoryCacheObject = _cache.lastObject() else { break } 506 | lastObject = newLastObject 507 | } 508 | } 509 | } 510 | 511 | func _unsafeSet(object: AnyObject, forKey key: String, cost: UInt = 0) { 512 | _cache.set(object: MemoryCacheObject(key: key, value: object, cost: cost), forKey: key) 513 | if _cache.cost > _costLimit { 514 | _unsafeTrim(toCost: _costLimit) 515 | } 516 | if _cache.count > _countLimit { 517 | _unsafeTrim(toCount: _countLimit) 518 | } 519 | } 520 | 521 | fileprivate func _lock() { 522 | _ = _semaphoreLock.wait(timeout: DispatchTime.distantFuture) 523 | } 524 | 525 | fileprivate func _unlock() { 526 | _semaphoreLock.signal() 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maquannene/Track/aa76dd4857aaa0791cdd4ac3c4141686ab45431e/logo.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ![Language](https://img.shields.io/badge/language-Swift%203.0-orange.svg) 4 | [![Pod Version](http://img.shields.io/cocoapods/v/Track.svg?style=flat)](http://cocoadocs.org/docsets/Track/) 5 | [![Pod Platform](http://img.shields.io/cocoapods/p/Track.svg?style=flat)](http://cocoadocs.org/docsets/Track/) 6 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/maquannene/Track/blob/master/LICENSE) 8 | 9 | Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU. 10 | 11 | ## Features 12 | 13 | * Thread safe: Implement by `dispatch_semaphore_t lock` and `DISPATCH_QUEUE_CONCURRENT`. Cache methods are thread safe and no deadlock. 14 | 15 | * LRU: Implement by linkedlist, it`s fast. You can manage a cache through functions to limit size, age of entries and memory usage to eliminate least recently used object. 16 | 17 | * Support async and sync operation. 18 | 19 | * Cache implement `SequenceType` `Generator`, support `subscrip` `for ... in` `map` `flapmap` `filter`... 20 | 21 | ## Use 22 | 23 | **Base use** 24 | 25 | Support Sync and Async Set, Get, RemoveObject, RemoveAll and Subscript. 26 | 27 | ```swift 28 | let track = Cache.shareInstance 29 | 30 | track.set(object: "object", forKey: "key") 31 | 32 | track.object(forKey: "key") 33 | 34 | track.removeObject(forKey: "key") { (cache, key, object) in } 35 | 36 | track.removeAllObjects { (cache, key, object) in } 37 | 38 | track["key"] = "object" 39 | 40 | print(track["key"]) 41 | ``` 42 | 43 | **Other use** 44 | 45 | MemoryCache and DiskCache has feature of LRU, so they can eliminate least recently used object according `countLimit`, `costLimit` and `ageLimit`. 46 | 47 | ```swift 48 | let diskcache = DiskCache.shareInstance 49 | 50 | diskcache.countLimit = 20 51 | 52 | diskcache.costLimit = 1024 * 10 53 | 54 | let memorycache = MemoryCache.shareInstance 55 | 56 | memorycache.trim(toAge: 1000) { (cache, key, object) in } 57 | 58 | memorycache.trim(toCount: 10) { (cache, key, object) in } 59 | ``` 60 | 61 | **New features: SequenceType Generator** 62 | 63 | Cache support thread safe `for ... in` `map` `forEache`... 64 | 65 | ```swift 66 | let cache: Cache = Cache.shareInstance 67 | 68 | for i in 1 ... 5 { 69 | cache.set(object: "\(i)", forKey: "\(i)") 70 | } 71 | 72 | for object in cache { 73 | print(object) 74 | } 75 | ``` 76 | 77 | ``` 78 | output: ("5", 5) ("4", 4) ("3", 3) ("2", 2) ("1", 1) 79 | ``` 80 | 81 | ``` 82 | cache.forEach { 83 | print($0) 84 | } 85 | ``` 86 | 87 | ``` 88 | output: ("1", 1) ("2", 2) ("3", 3) ("4", 4) ("5", 5) 89 | ``` 90 | 91 | ``` 92 | let values = cache.map { return $0 } 93 | 94 | print(values) 95 | ``` 96 | 97 | ``` 98 | output: [("5", 5), ("4", 4), ("3", 3), ("2", 2), ("1", 1)] 99 | ``` 100 | 101 | ## Installation 102 | 103 | **CocoaPods** 104 | 105 | Support Swift 5.0 106 | 107 | ``` 108 | pod 'Track', :git => 'https://github.com/maquannene/Track.git', :branch => 'master' 109 | ``` 110 | 111 | **Manually** 112 | 113 | 1. Download and drop ```/Track``` folder in your project. 114 | 2. Congratulations! 115 | 116 | ## Thanks 117 | 118 | Thanks YYCache,PINCache very much. Some ideas from them. 119 | 120 | ## License 121 | 122 | Track is released under the MIT license. 123 | 124 | 如果来自天朝[点击查看](https://github.com/maquannene/Track/blob/master/%E5%A6%82%E6%9E%9C%E4%BD%A0%E5%9C%A8%E5%A4%A9%E6%9C%9D.md)更多实现细节文章 125 | -------------------------------------------------------------------------------- /如果你在天朝.md: -------------------------------------------------------------------------------- 1 | # [Swift Cache - Track 实现笔记](http://maquannene.github.io/2016/06/17/Swift%20Cache%20-%20Track/) 2 | 3 | ## 引言 4 | 5 | 开始萌生出要写这个 [**Cache**](https://github.com/maquannene/Track) 念头,是想要练习一下 `Swift` 这门语言,顺便实战 `GCD 达到多线程安全` 和思考 `如何写一个易用的库`。所以大概花了一个礼拜的时间完成了初级版,后续断断续续修补功能又花了两个礼拜,最终在 v1.2.0 的时候,达到了一个让我比较满意的程度。 6 | 7 | 这个库没有用到特别高深的技巧,也没有特别复杂的算法,但是完成的过程让我学习到了很多东西,如果你想要实战 GCD 的基本用法、又或者是想要学习库基本的设计等等,建议读下去。 8 | 9 | ## 动手写之前 10 | 11 | 在开始写这个库之前,我已经拜读过 Objective-C 的一些 Cache 的源码,例如 Star 比较多的 [TMCache](https://github.com/tumblr/TMCache) 以及它的改良版 [PINCache](https://github.com/pinterest/PINCache),以及功能不是那么强大的 EGOCache 和 SDImageCache,当然还有大名鼎鼎的 [YYCache](https://github.com/ibireme/YYCache)。相比之下 Swift 的此类库就相对少一些,[AwesomeCache](https://github.com/aschuch/AwesomeCache) 算是 Star 相对多一些的库了,其他类似 Haneke 功能不在对比的范围内。 12 | 13 | 我对几个功能齐全的库的同步读写做了一个大概的测试(这里没有将任何一个 Swift 库加入对比中,因为确实没有找到功能比较齐全的库,比如 AwesomeCache 是没有区分 Memory 和 Disk 的,并且功能比较少。AwesomeCache 的 Memory 直接用的是 NSCache,所以我将 NSCache 加入了测试中)结果如下图: 14 | 15 | 下图为 `MemoryCache` 对随机产生的不重复 key value 数组进行读写测试: 16 | 17 |

18 | 19 |

20 | 21 | YY 和 Track 内部都采用了 LRU 淘汰算法,PIN 和 TM 有简单的淘汰功能,但并没有引入 LRU 算法,所以在写入后的淘汰数据阶段 YY 和 Track 要快于其他 Cache 的重排序淘汰。其中 TM 速度非常慢,原因在于 TM 的 GCD 调度策略存在很大的问题,会导致同步小数据读写性能都损耗在 GCD 的调度上。这里值得一说的是 NSCache 对随机 key value 的读写性能不错,尤其是读,但是一旦出现相似形数据,性能就会变得非常低。 22 | 23 | 下图为 `DiskCache` 对随机产生的不重复 key value 数组进行读写测试: 24 | 25 |

26 | 27 |

28 | 29 | 很明显,底层采用 sqlite 的 YY 性能要高于其他所有基于文件系统的库,所以这里基本可以分为 YYDiskCache 和 其他DiskCache。 30 | 31 | ## 开始动手写 32 | 33 | 在动手之前,已经了解到了各个库的优劣,所以在写的时候,我尽量提取了一些优点融入了 Track 中,接下来会主要针对以下几点进行说明,某些点对缓存的性能起到了决定性的作用: 34 | 35 | ### 1.线程调度 36 | 37 | 良好的线程调度,是高性能的一个重要保证,如果没有使用良好的线程调度,就会造成上图中 TMMemoryCache 那种结果。(下面都是针对同步操作的效率的讨论,异步操作讨论意义不大) 38 | 39 | 因为 Cache 要保证多线程安全,那么就必须有一套好的线程调度,经过一些源码的研究,我发现大部分缓存采取的线程调度策略分为下面两种: 40 | 41 | **方式一:** `并发队列 + barrier` + `信号量等待` 或 `串行队列` + `信号量等待` 42 | 43 | - 异步操作方式: 44 | * 读:异步到操作队列调用非线程安全读操作(例 TMMemoryCache 或 TMDiskCache) 45 | 46 | ```Objective-C 47 | dispatch_async(_queue, ^{ 48 | // 非线程安全读操作 49 | // objectForKey... 50 | }); 51 | ``` 52 | * 写: 53 | 54 | 如果操作队列为并发队列,使用 barrier_async 调用非线程安全写(例 TMMemoryCache) 55 | 56 | ```Objective-C 57 | dispatch_barrier_async(_currentQueue, ^{ 58 | // 非线程安全写操作 59 | // setObjectForKey 60 | }); 61 | 62 | ``` 63 | 如果操作队列为串行队列,那么只需 async 调用非线程安全写,不需要加 barrier 64 | 65 | ```Objective-C 66 | dispatch_async(_serialQueue, ^{ 67 | // 非线程安全写操作 68 | // setObjectForKey 69 | }); 70 | ``` 71 | 72 | - 同步操作方式:调用上述异步操作方式,外部加信号量锁,变同步(例:TMMemoryCache 或 TMDiskCache) 73 | 74 | ```Objective-C 75 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 76 | // 线程安全异步读写 77 | async_thread_safe_write_or_read(^{ 78 | dispatch_semaphore_signal(semaphore); 79 | }); 80 | // 等待信号变同步 81 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 82 | ``` 83 | 84 | **总结:**上述这种模型,如果使用的是`并发队列`,即 TMMemoryCache,最终能达到读取时支持大并发同步读,写入时用 barrier 保证了写入的原子性、并且和读操作之间的互斥性。 85 | 86 | `并发队列` + `barrier` 亦或者直接使用 `串行队列` 看似是一个十分完美的解决方案,但是实际上隐藏着很大的弊端,因为往往使用者会忽略掉线程切换造成的性能损耗。千万不要小看这一点损耗,试着想一下,如果我们写入或者读取的数据非常小,那么就会造成实际写入或者读取的时间远小于线程切换的时间,最终得不偿失。试图想要用并发队列使同一时间尽可能多的执行任务,以提高效率,但实际却发现时间全部消耗在了线程切换上。 87 | 88 | 同样的道理就是类似下面这种代码: 89 | 90 | ```Objective-C 91 | dispatch_apply(count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) { 92 | operation(); 93 | } 94 | // 这里使用 dispatch_apply 放入并发队列执行,如过 operation() 并不是非常耗时,不如直接使用 for loop 95 | 96 | ``` 97 | 总结一句话就是:如果发现实际的操作并不是非常耗时,就尽量不要用多线程去优化性能,否则大多数时间反而会消耗在线程切换上。 98 | 99 | 除此之外,使用这种模型时如果采用的是`并发队列 + barrier` + `信号量等待`,当大量并发的调用同步读写时,会造成死锁的问题。 100 | 101 | **方式二:** `并发队列` + `锁` 102 | 103 | - 同步操作方式: 104 | * 读:当前线程直接加锁读。 105 | * 写:当前线程直接加锁写。 106 | - 异步操作方式: 107 | * 读:异步到并发队列加锁读。 108 | * 写:异步到并发队列加锁写。 109 | * 其实就是异步到并发队列调用上然后调用同步读写。 110 | 111 | Track、YYCache、PINCache 都采用这种线程安全模型,简单点说就是最终的读写操作都加高性能锁,保证每次最终的读写都互斥。相比于方式一,首先解决的问题就是同步操作的效率问题,因为都是在当前线程直接进行读写操作,没有任何线程调度,所以省去了开辟和线程切换的开销,同步读写性能远远高于方式一。其次解决了死锁的为题,即使大量并发调用同步读写时,因为没有了方式一的信号量等待使异步变同步,并不会造成线程资源饱和导致无法解锁信号量导致死锁的问题。 112 | 113 | 相比于方式一,方式二其实并不支持真正的并发同步读,因为最终读操作都是加锁的,所以每个读都互斥,而方式一是可以做到并发读。但是鉴于方式一的策略本身就有死锁的问题,并且这些并不能提高效率的并发操作也是建立在有死锁的风险上,所以方案并不可取。 114 | 115 | Track 使用方式二,`MemoryCache` `DiskCache` 文件的类是线程安全的,`LinkList` 中的类是非线程安全的。`MemoryCache` `DiskCache` 文件中在使用 `LinkList` 中的类时做了线程安全封装。 116 | 117 | ### 2.淘汰策略 118 | 119 | 缓存的另一个功能是淘汰,每次设置数据完成后,都要对 count(总数) 和 cost(总内存占有量) 超出的部分进行移除,这两个淘汰功能所依据的条件是缓存对象的年龄,即 count 和 cost 淘汰每次从最老的数据开始移除。所以如何对对象年龄进行排序,也是决定性能好坏的因素之一。 120 | 121 | 在我读过的上面几个库中,实现淘汰的就只有 TM PIN 和 YY,TM 和 PIN 有 cost 和 date 淘汰,YY 和 Track 支持 count、cost、date 淘汰,实现方式分为两种如下: 122 | 123 | **方式一:**每次需要淘汰时重新排序,然后从最最老的数据开始移除 124 | 125 | TM 和 PIN 各自有一个记录每个存储对象 date 的字典,每次写入之后的淘汰都是基于对这个数组重新排序然后开始末尾移除。 126 | 127 | 这样做的劣势很明显,就是每次都会重新进行排序,如果你在使用 PINMemoryCache 时,设置了 costLimit 属性,那么你会发现效率立刻从摩托变成了单车,只能用惨不忍睹来形容,并且随着存储对象数量增加,会变得越来越慢。 128 | 129 | **方式二:**使用链表也存储一份对象模型的引用,用于排序淘汰 130 | 131 | 链表数据结构的优势是在已知节点的前提下,插入、移动、和删除的时间复杂度都是O(1),所以可以借助这个优势用实现 LRU。每次读取数据时,从字典中查询节点,然后在链表中将节点移到头部,写入数据后的淘汰就避免了重新根据年龄排序耗时的问题,直接从链表的末尾开始向前移除即可。 132 | 133 | YYMemoryCache 和 Track.MemoryCache 都采用了这种方式,稍微有点不同的是,YY 的 cost 淘汰是异步到主线程调用的,Track 的 cost 是当前线程直接调用,这里各自的利弊就不多做评价了,各有各的想法吧,[具体讨论可以看这里](https://github.com/ibireme/YYCache/issues/54)。另外一点是很值得学习的,YY 在 count 淘汰时,是可选异步 release,设置 `releaseAsynchronously` 即可,这里需要提醒的是,我在小对象写入性能测试时,发现如果设置了 countLimit,YYMemoryCache 的效率就有明显下降,Profile 显示原因在异步到其他线程的过程消耗了一些性能,所以存储对象都为小对象且有 countLimit 时,建议将 releaseAsynchronously 设置为 NO。 134 | 135 | ### 3.容器选取及对象存储模型 136 | 137 | #### 容器选取 138 | 139 | **(1)Memory** 140 | 141 | Memory 存储容器大致有这几种:NSCache,NSMutableDic,CFMutableDic 142 | 143 | NSCache 和 Dic 的区别在于 NSCache 本身就是线程安全的,所以上层不再需要安全线程调度的保护,而 Dic 是非线程安全的。AwesomeCache 就是采用 NSCache 直接作为内存缓存,NSCache 的效率上面的统计图已经给出,这里就不多说了。我在写 Track 时,本身第一想法是用 Dictionary,但是发现它的效率没有 NSMutableDic 高,所以最终选取了 NSMutableDic 作为存储和查询容器,LinkedList 作为调整顺序的容器。 144 | 145 | **(2)Disk** 146 | 147 | 毫无疑问,文件系统的效率远远小于数据库。由于对数据库并不是很了解,所以关于 Disk 部分就不做讨论了,可以去看 YY 的作者写的文章,里面对磁盘存储做了很好的讨论。 148 | 149 | #### 存储模型 150 | 151 | 这里想说的其实就是 TM 和 PIN 存在的另外一个性能问题,TM 和 PIN 在写入 Memory 时,除了写入本身存储 object 的 dictionary,还另外有两个字典 dates 和 costs。看源码中,会发现每次写入 object 时,都会对这三个字典写入一遍,性能分析时,发现写入的时间基本被这三个操作平分,性能立刻下降一大截。 152 | 153 | Track 和 YY 则采用的是将 date 和 cost 都封装起来,每次写入或修改数据时,只用写或者读一次字典,这样性能就提升了很多。 154 | 155 | ### 4. 追求性能慎用闭包 156 | 157 | 我在刚开始写 Track 的同步操作时,发现每次保证线程安全都需要加信号量锁,所以每次的代码都是这样的: 158 | 159 | ``` 160 | lock() 161 | ... 162 | unlock() 163 | 164 | ``` 165 | 166 | 我觉得这样实在是太不优雅了,所以写了个这样的函数: 167 | 168 | ``` 169 | func threadSafe(handler: () -> Void) { 170 | lock() 171 | handler() 172 | unlock() 173 | } 174 | ``` 175 | 176 | 后期我在优化性能时,发现闭包简直就是一个性能杀手,最后还是老老实实的前后调用加解锁函数。 177 | 178 | ## 功能增加 179 | 180 | 在写到 1.0 版本之后,我就在想,既然库是用 Swift 写的,如果没有一点 Swift 的功能,那岂不是等于抄袭别人写的代码然后翻译了一遍?所以就加入了些 Swift 的东西: 181 | 182 | ### 支持 GeneratorType、SequenceType 183 | 184 | 我给 Track 中底层的的 LinkedList 和 LRU 实现了 `GeneratorType` 和 `SequenceType` 接口,这样 Track 上层封装后也支持了线程安全的 `GeneratorType` 和 `SequenceType`。这样就 Track 的 Cache、Memory、DiskMemory 都支持了 `for ... in` `map` `flapmap` `filter` 等一系列方法,功能更加强大啦。 185 | 186 | 这里值得一说的是,Disk 的 Generator 有实现的是 `FastGeneratorType`,这样使得 Cache 遍历时,只要 MemoryCache 能读出值,DiskCache 指针快速移动即可,效率并不会降低,当 MemoryCache 读不到内容时,便开始读 DiskCache,并且数据衔结正确。 187 | 188 | ### 支持 Subscript 189 | 190 | ```Swift 191 | let cache: Cache = Cache.shareInstance 192 | cache["key"] = "value" 193 | let _ = cache["key"] 194 | ``` 195 | 读写操作更方便。 196 | 197 | ## 写在最后 198 | 199 | 目前就功能和性能上来讲,Objective-C 的此类 Cache 库中,YYCache 绝对是最好的。Swift 中目前我还没有找同类功能较齐全的库,AwesomeCache 只拥有基本的功能,所以如果你在写 Swift 的项目,正巧需要一个 Cache,那么请使用 [**Track**](https://github.com/maquannene/Track) 吧。 200 | 201 | [**Track**](https://github.com/maquannene/Track) 还是花了我一些心思去尽量写好它,包括前期调研、后期加功能以及优化等等,以后仍将继续维护。 202 | 203 | 所以支持的话,就👍一下吧。 204 | 205 | --------------------------------------------------------------------------------