├── LYCache.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── LastDays.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── LastDays.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── LYCache.xcscheme │ └── xcschememanagement.plist ├── LYCache ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── author.imageset │ │ ├── Contents.json │ │ └── author.jpg ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Cache │ ├── LDSImageCache.h │ └── LDSImageCache.m ├── Category │ ├── UIImageView+Cache.h │ └── UIImageView+Cache.m ├── Info.plist ├── LYCache.xcdatamodeld │ ├── .xccurrentversion │ └── LYCache.xcdatamodel │ │ └── contents ├── LYCacheImage │ └── DownImage │ │ ├── LDSDownloaderOperation.h │ │ ├── LDSDownloaderOperation.m │ │ ├── LDSImageDownloader.h │ │ └── LDSYImageDownloader.m ├── Manage │ ├── LDSCacheManage.h │ └── LDSCacheManage.m ├── ViewController.h ├── ViewController.m └── main.m ├── LYCacheTests ├── Info.plist └── LYCacheTests.m ├── LYCacheUITests ├── Info.plist └── LYCacheUITests.m ├── README.md └── image ├── 3.gif └── cache └── 3.gif /LYCache.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6C545C621CF01F6300F6EA56 /* LDSCacheManage.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C545C611CF01F6300F6EA56 /* LDSCacheManage.m */; }; 11 | 6C545C671CF1674900F6EA56 /* UIImageView+Cache.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C545C661CF1674900F6EA56 /* UIImageView+Cache.m */; }; 12 | 6C98014C1CBFC6AA00DE47A8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C98014B1CBFC6AA00DE47A8 /* main.m */; }; 13 | 6C98014F1CBFC6AA00DE47A8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C98014E1CBFC6AA00DE47A8 /* AppDelegate.m */; }; 14 | 6C9801521CBFC6AA00DE47A8 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C9801511CBFC6AA00DE47A8 /* ViewController.m */; }; 15 | 6C9801551CBFC6AA00DE47A8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6C9801531CBFC6AA00DE47A8 /* Main.storyboard */; }; 16 | 6C9801581CBFC6AA00DE47A8 /* LYCache.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6C9801561CBFC6AA00DE47A8 /* LYCache.xcdatamodeld */; }; 17 | 6C98015A1CBFC6AA00DE47A8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6C9801591CBFC6AA00DE47A8 /* Assets.xcassets */; }; 18 | 6C98015D1CBFC6AA00DE47A8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6C98015B1CBFC6AA00DE47A8 /* LaunchScreen.storyboard */; }; 19 | 6C9801681CBFC6AB00DE47A8 /* LYCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C9801671CBFC6AB00DE47A8 /* LYCacheTests.m */; }; 20 | 6C9801731CBFC6AB00DE47A8 /* LYCacheUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C9801721CBFC6AB00DE47A8 /* LYCacheUITests.m */; }; 21 | 6C9801831CBFCA7700DE47A8 /* LDSYImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C9801821CBFCA7700DE47A8 /* LDSYImageDownloader.m */; }; 22 | 6C9801861CC0980400DE47A8 /* LDSDownloaderOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C9801851CC0980300DE47A8 /* LDSDownloaderOperation.m */; }; 23 | 6CFCEE4E1CC727B30090E27C /* LDSImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CFCEE4D1CC727B30090E27C /* LDSImageCache.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 6C9801641CBFC6AB00DE47A8 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 6C98013F1CBFC6AA00DE47A8 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 6C9801461CBFC6AA00DE47A8; 32 | remoteInfo = LYCache; 33 | }; 34 | 6C98016F1CBFC6AB00DE47A8 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 6C98013F1CBFC6AA00DE47A8 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 6C9801461CBFC6AA00DE47A8; 39 | remoteInfo = LYCache; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 6C545C601CF01F6300F6EA56 /* LDSCacheManage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LDSCacheManage.h; path = Manage/LDSCacheManage.h; sourceTree = ""; }; 45 | 6C545C611CF01F6300F6EA56 /* LDSCacheManage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LDSCacheManage.m; path = Manage/LDSCacheManage.m; sourceTree = ""; }; 46 | 6C545C651CF1674900F6EA56 /* UIImageView+Cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImageView+Cache.h"; path = "Category/UIImageView+Cache.h"; sourceTree = ""; }; 47 | 6C545C661CF1674900F6EA56 /* UIImageView+Cache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+Cache.m"; path = "Category/UIImageView+Cache.m"; sourceTree = ""; }; 48 | 6C9801471CBFC6AA00DE47A8 /* LYCache.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LYCache.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 6C98014B1CBFC6AA00DE47A8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 50 | 6C98014D1CBFC6AA00DE47A8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 51 | 6C98014E1CBFC6AA00DE47A8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 52 | 6C9801501CBFC6AA00DE47A8 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 53 | 6C9801511CBFC6AA00DE47A8 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 54 | 6C9801541CBFC6AA00DE47A8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 6C9801571CBFC6AA00DE47A8 /* LYCache.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LYCache.xcdatamodel; sourceTree = ""; }; 56 | 6C9801591CBFC6AA00DE47A8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | 6C98015C1CBFC6AA00DE47A8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 58 | 6C98015E1CBFC6AA00DE47A8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 6C9801631CBFC6AB00DE47A8 /* LYCacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LYCacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 6C9801671CBFC6AB00DE47A8 /* LYCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LYCacheTests.m; sourceTree = ""; }; 61 | 6C9801691CBFC6AB00DE47A8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 6C98016E1CBFC6AB00DE47A8 /* LYCacheUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LYCacheUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 6C9801721CBFC6AB00DE47A8 /* LYCacheUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LYCacheUITests.m; sourceTree = ""; }; 64 | 6C9801741CBFC6AB00DE47A8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 6C9801811CBFCA7700DE47A8 /* LDSImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LDSImageDownloader.h; path = LYCacheImage/DownImage/LDSImageDownloader.h; sourceTree = ""; }; 66 | 6C9801821CBFCA7700DE47A8 /* LDSYImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LDSYImageDownloader.m; path = LYCacheImage/DownImage/LDSYImageDownloader.m; sourceTree = ""; }; 67 | 6C9801841CC0980300DE47A8 /* LDSDownloaderOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LDSDownloaderOperation.h; path = LYCacheImage/DownImage/LDSDownloaderOperation.h; sourceTree = ""; }; 68 | 6C9801851CC0980300DE47A8 /* LDSDownloaderOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LDSDownloaderOperation.m; path = LYCacheImage/DownImage/LDSDownloaderOperation.m; sourceTree = ""; }; 69 | 6CFCEE4C1CC727B30090E27C /* LDSImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LDSImageCache.h; path = Cache/LDSImageCache.h; sourceTree = ""; }; 70 | 6CFCEE4D1CC727B30090E27C /* LDSImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LDSImageCache.m; path = Cache/LDSImageCache.m; sourceTree = ""; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | 6C9801441CBFC6AA00DE47A8 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 6C9801601CBFC6AB00DE47A8 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 6C98016B1CBFC6AB00DE47A8 /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 6C545C5F1CF01ED300F6EA56 /* Manage */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 6C545C601CF01F6300F6EA56 /* LDSCacheManage.h */, 102 | 6C545C611CF01F6300F6EA56 /* LDSCacheManage.m */, 103 | ); 104 | name = Manage; 105 | sourceTree = ""; 106 | }; 107 | 6C545C641CF1672200F6EA56 /* Category */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 6C545C651CF1674900F6EA56 /* UIImageView+Cache.h */, 111 | 6C545C661CF1674900F6EA56 /* UIImageView+Cache.m */, 112 | ); 113 | name = Category; 114 | sourceTree = ""; 115 | }; 116 | 6C98013E1CBFC6AA00DE47A8 = { 117 | isa = PBXGroup; 118 | children = ( 119 | 6C9801491CBFC6AA00DE47A8 /* LYCache */, 120 | 6C9801661CBFC6AB00DE47A8 /* LYCacheTests */, 121 | 6C9801711CBFC6AB00DE47A8 /* LYCacheUITests */, 122 | 6C9801481CBFC6AA00DE47A8 /* Products */, 123 | ); 124 | sourceTree = ""; 125 | }; 126 | 6C9801481CBFC6AA00DE47A8 /* Products */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 6C9801471CBFC6AA00DE47A8 /* LYCache.app */, 130 | 6C9801631CBFC6AB00DE47A8 /* LYCacheTests.xctest */, 131 | 6C98016E1CBFC6AB00DE47A8 /* LYCacheUITests.xctest */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | 6C9801491CBFC6AA00DE47A8 /* LYCache */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 6C9801801CBFC96000DE47A8 /* LYCacheImage */, 140 | 6C98014D1CBFC6AA00DE47A8 /* AppDelegate.h */, 141 | 6C98014E1CBFC6AA00DE47A8 /* AppDelegate.m */, 142 | 6C9801501CBFC6AA00DE47A8 /* ViewController.h */, 143 | 6C9801511CBFC6AA00DE47A8 /* ViewController.m */, 144 | 6C9801531CBFC6AA00DE47A8 /* Main.storyboard */, 145 | 6C9801591CBFC6AA00DE47A8 /* Assets.xcassets */, 146 | 6C98015B1CBFC6AA00DE47A8 /* LaunchScreen.storyboard */, 147 | 6C98015E1CBFC6AA00DE47A8 /* Info.plist */, 148 | 6C9801561CBFC6AA00DE47A8 /* LYCache.xcdatamodeld */, 149 | 6C98014A1CBFC6AA00DE47A8 /* Supporting Files */, 150 | ); 151 | path = LYCache; 152 | sourceTree = ""; 153 | }; 154 | 6C98014A1CBFC6AA00DE47A8 /* Supporting Files */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 6C98014B1CBFC6AA00DE47A8 /* main.m */, 158 | ); 159 | name = "Supporting Files"; 160 | sourceTree = ""; 161 | }; 162 | 6C9801661CBFC6AB00DE47A8 /* LYCacheTests */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 6C9801671CBFC6AB00DE47A8 /* LYCacheTests.m */, 166 | 6C9801691CBFC6AB00DE47A8 /* Info.plist */, 167 | ); 168 | path = LYCacheTests; 169 | sourceTree = ""; 170 | }; 171 | 6C9801711CBFC6AB00DE47A8 /* LYCacheUITests */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 6C9801721CBFC6AB00DE47A8 /* LYCacheUITests.m */, 175 | 6C9801741CBFC6AB00DE47A8 /* Info.plist */, 176 | ); 177 | path = LYCacheUITests; 178 | sourceTree = ""; 179 | }; 180 | 6C9801801CBFC96000DE47A8 /* LYCacheImage */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 6C545C641CF1672200F6EA56 /* Category */, 184 | 6C545C5F1CF01ED300F6EA56 /* Manage */, 185 | 6CFCEE4B1CC658630090E27C /* Cache */, 186 | 6C9801871CC0984400DE47A8 /* Download */, 187 | ); 188 | name = LYCacheImage; 189 | sourceTree = ""; 190 | }; 191 | 6C9801871CC0984400DE47A8 /* Download */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 6C9801811CBFCA7700DE47A8 /* LDSImageDownloader.h */, 195 | 6C9801821CBFCA7700DE47A8 /* LDSYImageDownloader.m */, 196 | 6C9801841CC0980300DE47A8 /* LDSDownloaderOperation.h */, 197 | 6C9801851CC0980300DE47A8 /* LDSDownloaderOperation.m */, 198 | ); 199 | name = Download; 200 | sourceTree = ""; 201 | }; 202 | 6CFCEE4B1CC658630090E27C /* Cache */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | 6CFCEE4C1CC727B30090E27C /* LDSImageCache.h */, 206 | 6CFCEE4D1CC727B30090E27C /* LDSImageCache.m */, 207 | ); 208 | name = Cache; 209 | sourceTree = ""; 210 | }; 211 | /* End PBXGroup section */ 212 | 213 | /* Begin PBXNativeTarget section */ 214 | 6C9801461CBFC6AA00DE47A8 /* LYCache */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = 6C9801771CBFC6AB00DE47A8 /* Build configuration list for PBXNativeTarget "LYCache" */; 217 | buildPhases = ( 218 | 6C9801431CBFC6AA00DE47A8 /* Sources */, 219 | 6C9801441CBFC6AA00DE47A8 /* Frameworks */, 220 | 6C9801451CBFC6AA00DE47A8 /* Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | ); 226 | name = LYCache; 227 | productName = LYCache; 228 | productReference = 6C9801471CBFC6AA00DE47A8 /* LYCache.app */; 229 | productType = "com.apple.product-type.application"; 230 | }; 231 | 6C9801621CBFC6AB00DE47A8 /* LYCacheTests */ = { 232 | isa = PBXNativeTarget; 233 | buildConfigurationList = 6C98017A1CBFC6AB00DE47A8 /* Build configuration list for PBXNativeTarget "LYCacheTests" */; 234 | buildPhases = ( 235 | 6C98015F1CBFC6AB00DE47A8 /* Sources */, 236 | 6C9801601CBFC6AB00DE47A8 /* Frameworks */, 237 | 6C9801611CBFC6AB00DE47A8 /* Resources */, 238 | ); 239 | buildRules = ( 240 | ); 241 | dependencies = ( 242 | 6C9801651CBFC6AB00DE47A8 /* PBXTargetDependency */, 243 | ); 244 | name = LYCacheTests; 245 | productName = LYCacheTests; 246 | productReference = 6C9801631CBFC6AB00DE47A8 /* LYCacheTests.xctest */; 247 | productType = "com.apple.product-type.bundle.unit-test"; 248 | }; 249 | 6C98016D1CBFC6AB00DE47A8 /* LYCacheUITests */ = { 250 | isa = PBXNativeTarget; 251 | buildConfigurationList = 6C98017D1CBFC6AB00DE47A8 /* Build configuration list for PBXNativeTarget "LYCacheUITests" */; 252 | buildPhases = ( 253 | 6C98016A1CBFC6AB00DE47A8 /* Sources */, 254 | 6C98016B1CBFC6AB00DE47A8 /* Frameworks */, 255 | 6C98016C1CBFC6AB00DE47A8 /* Resources */, 256 | ); 257 | buildRules = ( 258 | ); 259 | dependencies = ( 260 | 6C9801701CBFC6AB00DE47A8 /* PBXTargetDependency */, 261 | ); 262 | name = LYCacheUITests; 263 | productName = LYCacheUITests; 264 | productReference = 6C98016E1CBFC6AB00DE47A8 /* LYCacheUITests.xctest */; 265 | productType = "com.apple.product-type.bundle.ui-testing"; 266 | }; 267 | /* End PBXNativeTarget section */ 268 | 269 | /* Begin PBXProject section */ 270 | 6C98013F1CBFC6AA00DE47A8 /* Project object */ = { 271 | isa = PBXProject; 272 | attributes = { 273 | LastUpgradeCheck = 0720; 274 | ORGANIZATIONNAME = LastDays; 275 | TargetAttributes = { 276 | 6C9801461CBFC6AA00DE47A8 = { 277 | CreatedOnToolsVersion = 7.2.1; 278 | DevelopmentTeam = 637MP4Y88D; 279 | }; 280 | 6C9801621CBFC6AB00DE47A8 = { 281 | CreatedOnToolsVersion = 7.2.1; 282 | DevelopmentTeam = 637MP4Y88D; 283 | TestTargetID = 6C9801461CBFC6AA00DE47A8; 284 | }; 285 | 6C98016D1CBFC6AB00DE47A8 = { 286 | CreatedOnToolsVersion = 7.2.1; 287 | DevelopmentTeam = 637MP4Y88D; 288 | TestTargetID = 6C9801461CBFC6AA00DE47A8; 289 | }; 290 | }; 291 | }; 292 | buildConfigurationList = 6C9801421CBFC6AA00DE47A8 /* Build configuration list for PBXProject "LYCache" */; 293 | compatibilityVersion = "Xcode 3.2"; 294 | developmentRegion = English; 295 | hasScannedForEncodings = 0; 296 | knownRegions = ( 297 | en, 298 | Base, 299 | ); 300 | mainGroup = 6C98013E1CBFC6AA00DE47A8; 301 | productRefGroup = 6C9801481CBFC6AA00DE47A8 /* Products */; 302 | projectDirPath = ""; 303 | projectRoot = ""; 304 | targets = ( 305 | 6C9801461CBFC6AA00DE47A8 /* LYCache */, 306 | 6C9801621CBFC6AB00DE47A8 /* LYCacheTests */, 307 | 6C98016D1CBFC6AB00DE47A8 /* LYCacheUITests */, 308 | ); 309 | }; 310 | /* End PBXProject section */ 311 | 312 | /* Begin PBXResourcesBuildPhase section */ 313 | 6C9801451CBFC6AA00DE47A8 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 6C98015D1CBFC6AA00DE47A8 /* LaunchScreen.storyboard in Resources */, 318 | 6C98015A1CBFC6AA00DE47A8 /* Assets.xcassets in Resources */, 319 | 6C9801551CBFC6AA00DE47A8 /* Main.storyboard in Resources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | 6C9801611CBFC6AB00DE47A8 /* Resources */ = { 324 | isa = PBXResourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | 6C98016C1CBFC6AB00DE47A8 /* Resources */ = { 331 | isa = PBXResourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | /* End PBXResourcesBuildPhase section */ 338 | 339 | /* Begin PBXSourcesBuildPhase section */ 340 | 6C9801431CBFC6AA00DE47A8 /* Sources */ = { 341 | isa = PBXSourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | 6C9801521CBFC6AA00DE47A8 /* ViewController.m in Sources */, 345 | 6C9801831CBFCA7700DE47A8 /* LDSYImageDownloader.m in Sources */, 346 | 6C545C621CF01F6300F6EA56 /* LDSCacheManage.m in Sources */, 347 | 6CFCEE4E1CC727B30090E27C /* LDSImageCache.m in Sources */, 348 | 6C9801581CBFC6AA00DE47A8 /* LYCache.xcdatamodeld in Sources */, 349 | 6C9801861CC0980400DE47A8 /* LDSDownloaderOperation.m in Sources */, 350 | 6C98014F1CBFC6AA00DE47A8 /* AppDelegate.m in Sources */, 351 | 6C98014C1CBFC6AA00DE47A8 /* main.m in Sources */, 352 | 6C545C671CF1674900F6EA56 /* UIImageView+Cache.m in Sources */, 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | }; 356 | 6C98015F1CBFC6AB00DE47A8 /* Sources */ = { 357 | isa = PBXSourcesBuildPhase; 358 | buildActionMask = 2147483647; 359 | files = ( 360 | 6C9801681CBFC6AB00DE47A8 /* LYCacheTests.m in Sources */, 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | }; 364 | 6C98016A1CBFC6AB00DE47A8 /* Sources */ = { 365 | isa = PBXSourcesBuildPhase; 366 | buildActionMask = 2147483647; 367 | files = ( 368 | 6C9801731CBFC6AB00DE47A8 /* LYCacheUITests.m in Sources */, 369 | ); 370 | runOnlyForDeploymentPostprocessing = 0; 371 | }; 372 | /* End PBXSourcesBuildPhase section */ 373 | 374 | /* Begin PBXTargetDependency section */ 375 | 6C9801651CBFC6AB00DE47A8 /* PBXTargetDependency */ = { 376 | isa = PBXTargetDependency; 377 | target = 6C9801461CBFC6AA00DE47A8 /* LYCache */; 378 | targetProxy = 6C9801641CBFC6AB00DE47A8 /* PBXContainerItemProxy */; 379 | }; 380 | 6C9801701CBFC6AB00DE47A8 /* PBXTargetDependency */ = { 381 | isa = PBXTargetDependency; 382 | target = 6C9801461CBFC6AA00DE47A8 /* LYCache */; 383 | targetProxy = 6C98016F1CBFC6AB00DE47A8 /* PBXContainerItemProxy */; 384 | }; 385 | /* End PBXTargetDependency section */ 386 | 387 | /* Begin PBXVariantGroup section */ 388 | 6C9801531CBFC6AA00DE47A8 /* Main.storyboard */ = { 389 | isa = PBXVariantGroup; 390 | children = ( 391 | 6C9801541CBFC6AA00DE47A8 /* Base */, 392 | ); 393 | name = Main.storyboard; 394 | sourceTree = ""; 395 | }; 396 | 6C98015B1CBFC6AA00DE47A8 /* LaunchScreen.storyboard */ = { 397 | isa = PBXVariantGroup; 398 | children = ( 399 | 6C98015C1CBFC6AA00DE47A8 /* Base */, 400 | ); 401 | name = LaunchScreen.storyboard; 402 | sourceTree = ""; 403 | }; 404 | /* End PBXVariantGroup section */ 405 | 406 | /* Begin XCBuildConfiguration section */ 407 | 6C9801751CBFC6AB00DE47A8 /* Debug */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INT_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_UNREACHABLE_CODE = YES; 423 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 424 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 425 | COPY_PHASE_STRIP = NO; 426 | DEBUG_INFORMATION_FORMAT = dwarf; 427 | ENABLE_STRICT_OBJC_MSGSEND = YES; 428 | ENABLE_TESTABILITY = YES; 429 | GCC_C_LANGUAGE_STANDARD = gnu99; 430 | GCC_DYNAMIC_NO_PIC = NO; 431 | GCC_NO_COMMON_BLOCKS = YES; 432 | GCC_OPTIMIZATION_LEVEL = 0; 433 | GCC_PREPROCESSOR_DEFINITIONS = ( 434 | "DEBUG=1", 435 | "$(inherited)", 436 | ); 437 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 438 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 439 | GCC_WARN_UNDECLARED_SELECTOR = YES; 440 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 441 | GCC_WARN_UNUSED_FUNCTION = YES; 442 | GCC_WARN_UNUSED_VARIABLE = YES; 443 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 444 | MTL_ENABLE_DEBUG_INFO = YES; 445 | ONLY_ACTIVE_ARCH = YES; 446 | SDKROOT = iphoneos; 447 | TARGETED_DEVICE_FAMILY = "1,2"; 448 | }; 449 | name = Debug; 450 | }; 451 | 6C9801761CBFC6AB00DE47A8 /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ALWAYS_SEARCH_USER_PATHS = NO; 455 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 456 | CLANG_CXX_LIBRARY = "libc++"; 457 | CLANG_ENABLE_MODULES = YES; 458 | CLANG_ENABLE_OBJC_ARC = YES; 459 | CLANG_WARN_BOOL_CONVERSION = YES; 460 | CLANG_WARN_CONSTANT_CONVERSION = YES; 461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 462 | CLANG_WARN_EMPTY_BODY = YES; 463 | CLANG_WARN_ENUM_CONVERSION = YES; 464 | CLANG_WARN_INT_CONVERSION = YES; 465 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 466 | CLANG_WARN_UNREACHABLE_CODE = YES; 467 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 468 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 469 | COPY_PHASE_STRIP = NO; 470 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 471 | ENABLE_NS_ASSERTIONS = NO; 472 | ENABLE_STRICT_OBJC_MSGSEND = YES; 473 | GCC_C_LANGUAGE_STANDARD = gnu99; 474 | GCC_NO_COMMON_BLOCKS = YES; 475 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 476 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 477 | GCC_WARN_UNDECLARED_SELECTOR = YES; 478 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 479 | GCC_WARN_UNUSED_FUNCTION = YES; 480 | GCC_WARN_UNUSED_VARIABLE = YES; 481 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 482 | MTL_ENABLE_DEBUG_INFO = NO; 483 | SDKROOT = iphoneos; 484 | TARGETED_DEVICE_FAMILY = "1,2"; 485 | VALIDATE_PRODUCT = YES; 486 | }; 487 | name = Release; 488 | }; 489 | 6C9801781CBFC6AB00DE47A8 /* Debug */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 493 | INFOPLIST_FILE = LYCache/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 495 | PRODUCT_BUNDLE_IDENTIFIER = uit.lastdays.LYCache; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | }; 498 | name = Debug; 499 | }; 500 | 6C9801791CBFC6AB00DE47A8 /* Release */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 504 | INFOPLIST_FILE = LYCache/Info.plist; 505 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 506 | PRODUCT_BUNDLE_IDENTIFIER = uit.lastdays.LYCache; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | }; 509 | name = Release; 510 | }; 511 | 6C98017B1CBFC6AB00DE47A8 /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | BUNDLE_LOADER = "$(TEST_HOST)"; 515 | INFOPLIST_FILE = LYCacheTests/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 517 | PRODUCT_BUNDLE_IDENTIFIER = uit.lastdays.LYCacheTests; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LYCache.app/LYCache"; 520 | }; 521 | name = Debug; 522 | }; 523 | 6C98017C1CBFC6AB00DE47A8 /* Release */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | BUNDLE_LOADER = "$(TEST_HOST)"; 527 | INFOPLIST_FILE = LYCacheTests/Info.plist; 528 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 529 | PRODUCT_BUNDLE_IDENTIFIER = uit.lastdays.LYCacheTests; 530 | PRODUCT_NAME = "$(TARGET_NAME)"; 531 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LYCache.app/LYCache"; 532 | }; 533 | name = Release; 534 | }; 535 | 6C98017E1CBFC6AB00DE47A8 /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | INFOPLIST_FILE = LYCacheUITests/Info.plist; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 540 | PRODUCT_BUNDLE_IDENTIFIER = uit.lastdays.LYCacheUITests; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | TEST_TARGET_NAME = LYCache; 543 | USES_XCTRUNNER = YES; 544 | }; 545 | name = Debug; 546 | }; 547 | 6C98017F1CBFC6AB00DE47A8 /* Release */ = { 548 | isa = XCBuildConfiguration; 549 | buildSettings = { 550 | INFOPLIST_FILE = LYCacheUITests/Info.plist; 551 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 552 | PRODUCT_BUNDLE_IDENTIFIER = uit.lastdays.LYCacheUITests; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | TEST_TARGET_NAME = LYCache; 555 | USES_XCTRUNNER = YES; 556 | }; 557 | name = Release; 558 | }; 559 | /* End XCBuildConfiguration section */ 560 | 561 | /* Begin XCConfigurationList section */ 562 | 6C9801421CBFC6AA00DE47A8 /* Build configuration list for PBXProject "LYCache" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 6C9801751CBFC6AB00DE47A8 /* Debug */, 566 | 6C9801761CBFC6AB00DE47A8 /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | 6C9801771CBFC6AB00DE47A8 /* Build configuration list for PBXNativeTarget "LYCache" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 6C9801781CBFC6AB00DE47A8 /* Debug */, 575 | 6C9801791CBFC6AB00DE47A8 /* Release */, 576 | ); 577 | defaultConfigurationIsVisible = 0; 578 | defaultConfigurationName = Release; 579 | }; 580 | 6C98017A1CBFC6AB00DE47A8 /* Build configuration list for PBXNativeTarget "LYCacheTests" */ = { 581 | isa = XCConfigurationList; 582 | buildConfigurations = ( 583 | 6C98017B1CBFC6AB00DE47A8 /* Debug */, 584 | 6C98017C1CBFC6AB00DE47A8 /* Release */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | 6C98017D1CBFC6AB00DE47A8 /* Build configuration list for PBXNativeTarget "LYCacheUITests" */ = { 590 | isa = XCConfigurationList; 591 | buildConfigurations = ( 592 | 6C98017E1CBFC6AB00DE47A8 /* Debug */, 593 | 6C98017F1CBFC6AB00DE47A8 /* Release */, 594 | ); 595 | defaultConfigurationIsVisible = 0; 596 | defaultConfigurationName = Release; 597 | }; 598 | /* End XCConfigurationList section */ 599 | 600 | /* Begin XCVersionGroup section */ 601 | 6C9801561CBFC6AA00DE47A8 /* LYCache.xcdatamodeld */ = { 602 | isa = XCVersionGroup; 603 | children = ( 604 | 6C9801571CBFC6AA00DE47A8 /* LYCache.xcdatamodel */, 605 | ); 606 | currentVersion = 6C9801571CBFC6AA00DE47A8 /* LYCache.xcdatamodel */; 607 | path = LYCache.xcdatamodeld; 608 | sourceTree = ""; 609 | versionGroupType = wrapper.xcdatamodel; 610 | }; 611 | /* End XCVersionGroup section */ 612 | }; 613 | rootObject = 6C98013F1CBFC6AA00DE47A8 /* Project object */; 614 | } 615 | -------------------------------------------------------------------------------- /LYCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LYCache.xcodeproj/project.xcworkspace/xcuserdata/LastDays.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrLoong/LDSImageCache/8df1fec0d7ed587778fab1857aa03e9faf5ba86d/LYCache.xcodeproj/project.xcworkspace/xcuserdata/LastDays.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /LYCache.xcodeproj/xcuserdata/LastDays.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LYCache.xcodeproj/xcuserdata/LastDays.xcuserdatad/xcschemes/LYCache.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /LYCache.xcodeproj/xcuserdata/LastDays.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | LYCache.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6C9801461CBFC6AA00DE47A8 16 | 17 | primary 18 | 19 | 20 | 6C9801621CBFC6AB00DE47A8 21 | 22 | primary 23 | 24 | 25 | 6C98016D1CBFC6AB00DE47A8 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LYCache/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | 16 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 17 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 18 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 19 | 20 | - (void)saveContext; 21 | - (NSURL *)applicationDocumentsDirectory; 22 | 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /LYCache/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | // Saves changes in the application's managed object context before the application terminates. 44 | [self saveContext]; 45 | } 46 | 47 | #pragma mark - Core Data stack 48 | 49 | @synthesize managedObjectContext = _managedObjectContext; 50 | @synthesize managedObjectModel = _managedObjectModel; 51 | @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; 52 | 53 | - (NSURL *)applicationDocumentsDirectory { 54 | // The directory the application uses to store the Core Data store file. This code uses a directory named "uit.lastdays.LYCache" in the application's documents directory. 55 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 56 | } 57 | 58 | - (NSManagedObjectModel *)managedObjectModel { 59 | // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model. 60 | if (_managedObjectModel != nil) { 61 | return _managedObjectModel; 62 | } 63 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"LYCache" withExtension:@"momd"]; 64 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 65 | return _managedObjectModel; 66 | } 67 | 68 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 69 | // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. 70 | if (_persistentStoreCoordinator != nil) { 71 | return _persistentStoreCoordinator; 72 | } 73 | 74 | // Create the coordinator and store 75 | 76 | _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 77 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"LYCache.sqlite"]; 78 | NSError *error = nil; 79 | NSString *failureReason = @"There was an error creating or loading the application's saved data."; 80 | if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 81 | // Report any error we got. 82 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 83 | dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data"; 84 | dict[NSLocalizedFailureReasonErrorKey] = failureReason; 85 | dict[NSUnderlyingErrorKey] = error; 86 | error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; 87 | // Replace this with code to handle the error appropriately. 88 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 89 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 90 | abort(); 91 | } 92 | 93 | return _persistentStoreCoordinator; 94 | } 95 | 96 | 97 | - (NSManagedObjectContext *)managedObjectContext { 98 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) 99 | if (_managedObjectContext != nil) { 100 | return _managedObjectContext; 101 | } 102 | 103 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 104 | if (!coordinator) { 105 | return nil; 106 | } 107 | _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 108 | [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 109 | return _managedObjectContext; 110 | } 111 | 112 | #pragma mark - Core Data Saving support 113 | 114 | - (void)saveContext { 115 | NSManagedObjectContext *managedObjectContext = self.managedObjectContext; 116 | if (managedObjectContext != nil) { 117 | NSError *error = nil; 118 | if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { 119 | // Replace this implementation with code to handle the error appropriately. 120 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 121 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 122 | abort(); 123 | } 124 | } 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /LYCache/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 | } -------------------------------------------------------------------------------- /LYCache/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /LYCache/Assets.xcassets/author.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "author.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /LYCache/Assets.xcassets/author.imageset/author.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrLoong/LDSImageCache/8df1fec0d7ed587778fab1857aa03e9faf5ba86d/LYCache/Assets.xcassets/author.imageset/author.jpg -------------------------------------------------------------------------------- /LYCache/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 | -------------------------------------------------------------------------------- /LYCache/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 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /LYCache/Cache/LDSImageCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // LDImageCache.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/20. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | typedef NS_ENUM(NSInteger,ImageCacheType){ 14 | /** 15 | * 无类型 16 | */ 17 | ImageCacheTypeNone, 18 | /** 19 | * 磁盘中缓存 20 | */ 21 | ImageCacheTypeDisk, 22 | /** 23 | * 内存中缓存 24 | */ 25 | ImageCacheTypeMemory 26 | }; 27 | 28 | /** 29 | * CompletedBlock 30 | * 31 | * @param image 图片 32 | * @param error 错误信息 33 | * @param type 读取类型 34 | */ 35 | typedef void(^CompletedBlock)(UIImage *image,NSError *error,ImageCacheType type); 36 | 37 | /** 38 | * 无参Block 39 | */ 40 | typedef void(^NoParamsBlock)(); 41 | 42 | @interface LDSImageCache : NSObject 43 | 44 | /** 45 | * 最大缓存大小 46 | */ 47 | @property (assign, nonatomic) NSUInteger maxCacheSize; 48 | 49 | 50 | /** 51 | * 单例方法 52 | * 53 | * @return LDImageCache实例 54 | */ 55 | +(LDSImageCache *)shareLDImageCache; 56 | 57 | /** 58 | * 对图片进行缓存 59 | * 60 | * @param memoryCache 内存缓存 61 | * @param image 图片 62 | * @param imageData 图片Data 63 | * @param urlKey key值 64 | * @param isSaveToDisk 是否存入Disk 65 | */ 66 | -(void)saveImageWithMemoryCache:(NSCache *)memoryCache image:(UIImage *)image imageData:(NSData *)imageData urlKey:(NSString *)urlKey isSaveToDisk:(BOOL)isSaveToDisk; 67 | 68 | /** 69 | * 查询图片 70 | * 71 | * @param urlKey urlkey 72 | * @param completed CompletedBlock 73 | */ 74 | -(void)selectImageWithKey:(NSString *)urlKey completedBlock:(CompletedBlock)completed; 75 | 76 | 77 | /** 78 | * 清空全部 79 | * 80 | * @param completion completion 81 | */ 82 | - (void)clearDiskOnCompletion:(NoParamsBlock)completion; 83 | 84 | -(void)clearDiskWithNoParamsBlock:(NoParamsBlock)noParamsBlock; 85 | 86 | 87 | - (instancetype)initWithCacheSpace:(NSString *)path; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /LYCache/Cache/LDSImageCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // LDImageCache.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/20. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "LDSImageCache.h" 10 | #import 11 | 12 | 13 | @interface LDSImageCache () 14 | 15 | @property(strong,nonatomic) NSCache *memoryCache; 16 | @property(strong,nonatomic) NSString *diskCachePath; 17 | @property(strong,nonatomic) dispatch_queue_t ioSerialQueue; 18 | @property(strong,nonatomic) NSFileManager *fileMange; 19 | 20 | 21 | @end 22 | 23 | @implementation LDSImageCache 24 | 25 | 26 | +(LDSImageCache *)shareLDImageCache{ 27 | static LDSImageCache *ldImageCache; 28 | static dispatch_once_t onceToken; 29 | 30 | dispatch_once(&onceToken, ^{ 31 | ldImageCache = [[LDSImageCache alloc] init]; 32 | }); 33 | return ldImageCache; 34 | } 35 | 36 | - (id)init 37 | { 38 | return [self initWithCacheSpace:@"default"]; 39 | } 40 | 41 | 42 | - (instancetype)initWithCacheSpace:(NSString *)path 43 | { 44 | self = [super init]; 45 | if (self) { 46 | 47 | /** 48 | * 文件路径 49 | */ 50 | NSString *fullPath = [@"com.LastDays.LYCache." stringByAppendingString:path]; 51 | 52 | //创建IO串行队列 53 | _ioSerialQueue = dispatch_queue_create("com.LDImageCache.ioSerialQueue", DISPATCH_QUEUE_SERIAL); 54 | 55 | //初始化内存缓存 56 | _memoryCache = [[NSCache alloc] init]; 57 | _memoryCache.name = fullPath; 58 | 59 | //获取Cache目录路径,初始化磁盘缓存路径 60 | NSArray *cacPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 61 | _diskCachePath = [[cacPath objectAtIndex:0] stringByAppendingPathComponent:fullPath]; 62 | 63 | //初始化fileMange 64 | dispatch_sync(self.ioSerialQueue, ^{ 65 | _fileMange = [NSFileManager defaultManager]; 66 | }); 67 | } 68 | return self; 69 | } 70 | 71 | /** 72 | * 进行缓存 73 | * 74 | * @param memoryCache 内存 75 | * @param image 图片 76 | * @param imageData 图片data 77 | * @param urlKey key值就用来唯一标记数据 78 | * @param isSaveTOdisk 是否进行沙箱缓存 79 | */ 80 | -(void)saveImageWithMemoryCache:(NSCache *)memoryCache image:(UIImage *)image imageData:(NSData *)imageData urlKey:(NSString *)urlKey isSaveToDisk:(BOOL)isSaveToDisk{ 81 | 82 | //内存缓存 83 | if (memoryCache == nil) { 84 | 85 | [self.memoryCache setObject:image forKey:urlKey]; 86 | 87 | }else{ 88 | [memoryCache setObject:image forKey:urlKey]; 89 | } 90 | 91 | //磁盘缓存 92 | if (isSaveToDisk) { 93 | dispatch_sync(self.ioSerialQueue, ^{ 94 | if (![_fileMange fileExistsAtPath:_diskCachePath]) { 95 | [_fileMange createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:nil]; 96 | } 97 | NSString *pathForKey = [self defaultCachePathForKey:urlKey]; 98 | 99 | NSLog(@"%@",pathForKey); 100 | 101 | [_fileMange createFileAtPath:pathForKey contents:imageData attributes:nil]; 102 | }); 103 | } 104 | } 105 | 106 | 107 | 108 | //查询图片 109 | -(void)selectImageWithKey:(NSString *)urlKey completedBlock:(CompletedBlock)completed{ 110 | UIImage *image = [self.memoryCache objectForKey:urlKey]; 111 | if (image != nil) { 112 | NSLog(@"ok"); 113 | completed(image,nil,ImageCacheTypeMemory); 114 | 115 | }else{ 116 | 117 | NSString *pathForKey = [self defaultCachePathForKey:urlKey]; 118 | NSLog(@"%@",pathForKey); 119 | NSData *imageData = [NSData dataWithContentsOfFile:pathForKey]; 120 | UIImage *diskImage = [UIImage imageWithData:imageData]; 121 | 122 | dispatch_async(dispatch_get_main_queue(), ^{ 123 | completed(diskImage,nil,ImageCacheTypeDisk); 124 | }); 125 | } 126 | } 127 | 128 | 129 | 130 | 131 | /** 132 | * 清空全部 133 | * 134 | * @param completion completion 135 | */ 136 | - (void)clearDiskOnCompletion:(NoParamsBlock)completion 137 | { 138 | dispatch_async(self.ioSerialQueue, ^{ 139 | [_fileMange removeItemAtPath:self.diskCachePath error:nil]; 140 | [_fileMange createDirectoryAtPath:self.diskCachePath 141 | withIntermediateDirectories:YES 142 | attributes:nil 143 | error:NULL]; 144 | 145 | if (completion) { 146 | dispatch_async(dispatch_get_main_queue(), ^{ 147 | completion(); 148 | }); 149 | } 150 | }); 151 | } 152 | 153 | 154 | /** 155 | * 按条件进行清空(主要是时间) 156 | * 157 | * @param noParamsBlock completion 158 | */ 159 | -(void)clearDiskWithNoParamsBlock:(NoParamsBlock)noParamsBlock{ 160 | 161 | dispatch_async(self.ioSerialQueue, ^{ 162 | 163 | NSURL *diskCache = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; 164 | NSArray *resourcKeys = @[NSURLIsDirectoryKey,NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; 165 | 166 | 167 | 168 | // 1. 该枚举器预先获取缓存文件的有用的属性 169 | NSDirectoryEnumerator *fileEnumerator = [_fileMange enumeratorAtURL:diskCache 170 | includingPropertiesForKeys:resourcKeys 171 | options:NSDirectoryEnumerationSkipsHiddenFiles 172 | errorHandler:NULL]; 173 | 174 | NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-60 * 60 * 24 * 7]; 175 | NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; 176 | NSInteger currentCacheSize = 0; 177 | 178 | NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; 179 | 180 | for (NSURL *fileURL in fileEnumerator) { 181 | NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourcKeys error:NULL]; 182 | 183 | 184 | // 3. 跳过文件夹 185 | if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { 186 | continue; 187 | } 188 | 189 | NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; 190 | if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { 191 | [urlsToDelete addObject:fileURL]; 192 | 193 | continue; 194 | } 195 | 196 | 197 | // 5. 存储文件的引用并计算所有文件的总大小,以备后用 198 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 199 | currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; 200 | [cacheFiles setObject:resourceValues forKey:fileURL]; 201 | 202 | } 203 | 204 | for (NSURL *fileURL in urlsToDelete) { 205 | [self.fileMange removeItemAtURL:fileURL error:NULL]; 206 | } 207 | 208 | if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { 209 | const NSUInteger desiredCacheSize = self.maxCacheSize / 2; 210 | 211 | // Sort the remaining cache files by their last modification time (oldest first). 212 | NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent 213 | usingComparator:^NSComparisonResult(id obj1, id obj2) { 214 | return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; 215 | }]; 216 | 217 | // Delete files until we fall below our desired cache size. 218 | for (NSURL *fileURL in sortedFiles) { 219 | if ([_fileMange removeItemAtURL:fileURL error:nil]) { 220 | NSDictionary *resourceValues = cacheFiles[fileURL]; 221 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 222 | currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; 223 | 224 | if (currentCacheSize < desiredCacheSize) { 225 | break; 226 | } 227 | } 228 | } 229 | } 230 | if (noParamsBlock) { 231 | dispatch_async(dispatch_get_main_queue(), ^{ 232 | noParamsBlock(); 233 | }); 234 | } 235 | 236 | }); 237 | 238 | } 239 | 240 | 241 | 242 | /** 243 | * 盗用了SDWebImage的设计,将文件名按照MD5进行命名,保持唯一性 244 | * 245 | * @param key urlKey 246 | * 247 | * @return 文件名是对key值做MD5摘要后的串 248 | */ 249 | #pragma mark LDSImageCache (private) 250 | 251 | - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path { 252 | NSString *filename = [self cachedFileNameForKey:key]; 253 | return [path stringByAppendingPathComponent:filename]; 254 | } 255 | 256 | - (NSString *)defaultCachePathForKey:(NSString *)key { 257 | return [self cachePathForKey:key inPath:self.diskCachePath]; 258 | } 259 | 260 | - (NSString *)cachedFileNameForKey:(NSString *)key { 261 | const char *str = [key UTF8String]; 262 | if (str == NULL) { 263 | str = ""; 264 | } 265 | unsigned char r[16]; 266 | CC_MD5(str, (uint32_t)strlen(str), r); 267 | NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 268 | r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; 269 | 270 | return filename; 271 | } 272 | @end 273 | -------------------------------------------------------------------------------- /LYCache/Category/UIImageView+Cache.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Cache.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/5/22. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "LDSCacheManage.h" 12 | 13 | @interface UIImageView (WebImage) 14 | 15 | - (void)lds_setImageWithURL:(NSString *)url progressBlock:(DownloaderProgressBlock)progressBlock completed:(DownloaderCompletedBlock)completedBlock; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /LYCache/Category/UIImageView+Cache.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Cache.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/5/22. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "UIImageView+Cache.h" 10 | 11 | @implementation UIImageView(WebImage) 12 | 13 | - (void)lds_setImageWithURL:(NSString *)url progressBlock:(DownloaderProgressBlock)progressBlock completed:(DownloaderCompletedBlock)completedBlock{ 14 | __weak __typeof(self)wself = self; 15 | [[LDSCacheManage shareLDCacheManage] downImageWithURL:url 16 | DownloaderProgressBlock:^(NSInteger aleradyReceiveSize,NSInteger expectedContentLength){ 17 | if (progressBlock) { 18 | progressBlock(aleradyReceiveSize,expectedContentLength); 19 | } 20 | 21 | }DownloaderCompletedBlock:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 22 | dispatch_async(dispatch_get_main_queue(), ^{ 23 | wself.image = image; 24 | 25 | NSLog(@"%@",wself.image); 26 | 27 | [wself setNeedsLayout]; 28 | if (completedBlock) { 29 | completedBlock(data, image, error, YES); 30 | } 31 | }); 32 | 33 | }]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /LYCache/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 | NSAppTransportSecurity 40 | 41 | NSAllowsArbitraryLoads 42 | 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /LYCache/LYCache.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | LYCache.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /LYCache/LYCache.xcdatamodeld/LYCache.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LYCache/LYCacheImage/DownImage/LDSDownloaderOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // LYDownloaderOperation.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/15. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LDSImageDownloader.h" 11 | 12 | @interface LYDownloaderOperation : NSOperation 13 | 14 | /** 15 | * 回调信息 16 | */ 17 | @property (copy, nonatomic) DownloaderProgressBlock progressBlock; 18 | @property (copy, nonatomic) DownloaderCompletedBlock completedBlock; 19 | @property (copy, nonatomic) DownloaderCreateBlock cancelBlock; 20 | 21 | /** 22 | * 未接收大小 23 | */ 24 | @property(assign,nonatomic) NSInteger expectedContentLength; 25 | 26 | /** 27 | * 下载操作 28 | */ 29 | @property(assign,nonatomic) DownloaderOptions options; 30 | 31 | @property(strong,nonatomic) NSMutableURLRequest *request; 32 | 33 | 34 | - (instancetype)initWithRequest:(NSMutableURLRequest *)request 35 | DownloaderOptions:(DownloaderOptions)options 36 | DownloaderProgressBlock :(DownloaderProgressBlock)ProgressBlock 37 | DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock 38 | cancelled:(DownloaderCreateBlock)cancelledBlock; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LYCache/LYCacheImage/DownImage/LDSDownloaderOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // LYDownloaderOperation.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/15. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "LDSDownloaderOperation.h" 10 | 11 | @interface LYDownloaderOperation () 12 | 13 | 14 | @property(strong,nonatomic) NSMutableData *imageData; 15 | 16 | @end 17 | 18 | 19 | @implementation LYDownloaderOperation 20 | 21 | 22 | - (instancetype)initWithRequest:(NSMutableURLRequest *)request 23 | DownloaderOptions:(DownloaderOptions)options 24 | DownloaderProgressBlock :(DownloaderProgressBlock)ProgressBlock 25 | DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock 26 | cancelled:(DownloaderCreateBlock)cancelledBlock 27 | { 28 | self = [super init]; 29 | if (self) { 30 | _request = request; 31 | _cancelBlock = cancelledBlock; 32 | _progressBlock = ProgressBlock; 33 | _options = options; 34 | _completedBlock = completedBlock; 35 | _expectedContentLength = 0; 36 | } 37 | return self; 38 | } 39 | 40 | -(void)start{ 41 | NSLog(@"start"); 42 | 43 | if (self.isCancelled) { 44 | return; 45 | } 46 | 47 | /** 48 | * 创建NSURLSessionConfiguration类的对象, 这个对象被用于创建NSURLSession类的对象. 49 | */ 50 | NSURLSessionConfiguration *configura = [NSURLSessionConfiguration defaultSessionConfiguration]; 51 | 52 | /** 53 | * 2. 创建NSURLSession的对象. 54 | * 参数一 : NSURLSessionConfiguration类的对象.(第1步创建的对象.) 55 | * 参数二 : session的代理人. 如果为nil, 系统将会提供一个代理人. 56 | * 参数三 : 一个队列, 代理方法在这个队列中执行. 如果为nil, 系统会自动创建一系列的队列. 57 | * 注: 只能通过这个方法给session设置代理人, 因为在NSURLSession中delegate属性是只读的. 58 | */ 59 | NSURLSession *session = [NSURLSession sessionWithConfiguration:configura delegate:self delegateQueue:nil]; 60 | 61 | /** 62 | * 创建request 63 | */ 64 | NSMutableURLRequest *request = self.request; 65 | 66 | /** 67 | * 创建数据类型任务 68 | */ 69 | NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; 70 | 71 | /** 72 | * 开始任务 73 | */ 74 | [dataTask resume]; 75 | 76 | /** 77 | * 在session中的所有任务都完成之后, 使session失效. 78 | */ 79 | [session finishTasksAndInvalidate]; 80 | 81 | } 82 | 83 | 84 | -(void)cancel{ 85 | 86 | if (self.cancelBlock) { 87 | NSLog(@"结束线程"); 88 | self.cancelBlock(); 89 | [self clear]; 90 | } 91 | } 92 | 93 | //最先调用,在这里做一些数据的初始化。 94 | -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{ 95 | NSLog(@"开始"); 96 | self.imageData = [[NSMutableData alloc] init]; 97 | self.expectedContentLength = response.expectedContentLength; 98 | 99 | if (self.isCancelled) { 100 | _imageData = nil; 101 | } 102 | 103 | if (self.progressBlock) { 104 | self.progressBlock(0,self.expectedContentLength); 105 | } 106 | 107 | completionHandler(NSURLSessionResponseAllow); 108 | 109 | } 110 | 111 | //下载响应 112 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 113 | didReceiveData:(NSData *)data{ 114 | 115 | [self.imageData appendData:data]; 116 | if (self.progressBlock) { 117 | self.progressBlock(self.imageData.length,self.expectedContentLength); 118 | } 119 | 120 | 121 | } 122 | 123 | //下载完成后调用 124 | -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ 125 | if (!error) { 126 | UIImage *image = [UIImage imageWithData:self.imageData]; 127 | self.completedBlock(self.imageData,image,nil,YES); 128 | [self cancel]; 129 | }else{ 130 | self.completedBlock(self.imageData,nil,error,NO); 131 | [self cancel]; 132 | } 133 | 134 | } 135 | 136 | /** 137 | * 清空 138 | */ 139 | -(void)clear{ 140 | _request = nil; 141 | _cancelBlock = nil; 142 | _progressBlock = nil; 143 | _options = 1; 144 | _completedBlock = nil; 145 | _expectedContentLength = 0; 146 | } 147 | 148 | 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /LYCache/LYCacheImage/DownImage/LDSImageDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DownlodImage.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_OPTIONS(NSInteger,DownloaderOptions) { 13 | 14 | //默认下载操作 15 | DownloaderDefault = 1, 16 | 17 | //允许后台操作 18 | DownloaderContinueInBackground = 2 19 | }; 20 | 21 | typedef NS_ENUM(NSInteger,DownloaderOrder){ 22 | 23 | //默认下载顺序,先进先出 24 | DownloaderFIFO, 25 | 26 | //先进后出 27 | DownloaderLIFO 28 | }; 29 | 30 | /** 31 | * 无参数block 32 | */ 33 | typedef void(^DownloaderCreateBlock)(); 34 | 35 | /** 36 | * 下载回调信息,下载进度Block 37 | * 38 | * @param AlreadyReceiveSize 已经接收大小 39 | * @param NotReceiveSize 未接收大小 40 | */ 41 | typedef void(^DownloaderProgressBlock)(NSInteger alreadyReceiveSize,NSInteger expectedContentLength); 42 | 43 | /** 44 | * 下载回调信息,完成下载Block 45 | * 46 | * @param data data 47 | * @param image 图片 48 | * @param error 错误信息 49 | * @param finished 是否完成 50 | */ 51 | typedef void(^DownloaderCompletedBlock)(NSData *data,UIImage *image,NSError *error,BOOL finished); 52 | 53 | @interface LYImageDownloader : NSObject 54 | 55 | 56 | /** 57 | * 单例方法 58 | * 59 | * @return 返回一个全局的LYDownlodImage 60 | */ 61 | +(instancetype)shareDownloader; 62 | 63 | 64 | 65 | -(void)downloaderImageWithDownloaderWithURL:(NSURL *)url DownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock; 66 | 67 | 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /LYCache/LYCacheImage/DownImage/LDSYImageDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // DownlodImage.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "LDSImageDownloader.h" 10 | #import "LDSDownloaderOperation.h" 11 | 12 | 13 | @interface LYImageDownloader () 14 | 15 | /** 16 | * 下载队列 17 | */ 18 | @property(strong,nonatomic) NSOperationQueue *downloadQueue; 19 | 20 | /** 21 | * 将所有的下载回调信息存储在这里,Key是URL,Value是多组回调信息 22 | */ 23 | @property(strong,nonatomic) NSMutableDictionary *downloaderCallBack; 24 | 25 | @property(strong,nonatomic) dispatch_queue_t concurrentQueue; 26 | 27 | 28 | @end 29 | 30 | @implementation LYImageDownloader 31 | 32 | - (instancetype)init 33 | { 34 | self = [super init]; 35 | if (self) { 36 | _downloadQueue = [[NSOperationQueue alloc] init]; 37 | _downloadQueue.maxConcurrentOperationCount = 4; 38 | _concurrentQueue = dispatch_queue_create("com.lastdays.LYCache.ForBarrier", DISPATCH_QUEUE_CONCURRENT); 39 | _downloaderCallBack = [[NSMutableDictionary alloc] init]; 40 | } 41 | return self; 42 | } 43 | 44 | +(instancetype)shareDownloader{ 45 | 46 | static LYImageDownloader *lyImageDownloader; 47 | static dispatch_once_t onceToken; 48 | dispatch_once(&onceToken, ^{ 49 | lyImageDownloader = [[LYImageDownloader alloc] init]; 50 | }); 51 | return lyImageDownloader; 52 | } 53 | 54 | 55 | 56 | 57 | /** 58 | * 下载管理器对于下载请求的管理 59 | * 60 | * @param progressBlock DownloaderProgressBlock 61 | * @param completedBlock DownloaderCompletedBlock 62 | * @param url url 63 | */ 64 | -(void)downloaderImageWithDownloaderWithURL:(NSURL *)url DownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock{ 65 | 66 | __weak __typeof(self)myself = self; 67 | __block LYDownloaderOperation *operation; 68 | 69 | 70 | [self addWithDownloaderProgressBlock:progressBlock DownloaderCompletedBlock:completedBlock URL:url DownloaderCreateBlock:^{ 71 | 72 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30]; 73 | 74 | operation = [[LYDownloaderOperation alloc] initWithRequest:request 75 | DownloaderOptions:1 76 | DownloaderProgressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 77 | 78 | __block NSArray *urlCallBacks; 79 | 80 | dispatch_sync(self.concurrentQueue, ^{ 81 | urlCallBacks = [myself.downloaderCallBack[url] copy]; 82 | }); 83 | for (NSDictionary *callbacks in urlCallBacks) { 84 | dispatch_async(dispatch_get_main_queue(), ^{ 85 | DownloaderProgressBlock progress = callbacks[@"progress"]; 86 | if (progress) { 87 | progress(alreadyReceiveSize,expectedContentLength); 88 | } 89 | }); 90 | } 91 | } 92 | DownloaderCompletedBlock:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 93 | __block NSArray *urlCallBacks; 94 | dispatch_barrier_sync(myself.concurrentQueue, ^{ 95 | urlCallBacks = [myself.downloaderCallBack[url] copy]; 96 | if (finished) { 97 | [myself.downloaderCallBack removeObjectForKey:url]; 98 | } 99 | }); 100 | 101 | for (NSDictionary *callBack in urlCallBacks) { 102 | dispatch_sync(self.concurrentQueue,^{ 103 | DownloaderCompletedBlock completed = callBack[@"completed"]; 104 | if (completed) { 105 | completed(data,image,error,finished); 106 | } 107 | }); 108 | 109 | } 110 | } 111 | cancelled:^{ 112 | dispatch_barrier_sync(myself.concurrentQueue, ^{ 113 | NSLog(@"取消操作"); 114 | [myself.downloaderCallBack removeObjectForKey:url]; 115 | }); 116 | 117 | }]; 118 | [myself.downloadQueue addOperation:operation]; 119 | 120 | }]; 121 | } 122 | 123 | 124 | /** 125 | * 添加回调信息 126 | * 127 | * @param progressBlock DownloaderProgressBlock 128 | * @param completedBlock DownloaderCompletedBlock 129 | * @param url url 130 | * @param DownloaderCreateBlock DownloaderCreateBlock 131 | */ 132 | -(void)addWithDownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock URL:(NSURL *)url DownloaderCreateBlock:(DownloaderCreateBlock)downloaderCreateBlock{ 133 | 134 | /** 135 | * 判断url是否为空 136 | */ 137 | if ([url isEqual:nil]) { 138 | completedBlock(nil,nil,nil,NO); 139 | } 140 | 141 | /** 142 | * 设置屏障,保证在同一时间,只有一个线程可以操作downloaderCallBack属性,保证在并行多个处理的时候,对downloaderCallBack属性的读写操作保持一致 143 | */ 144 | dispatch_barrier_sync(self.concurrentQueue, ^{ 145 | 146 | BOOL firstDownload = NO; 147 | /** 148 | * 添加回调信息,处理同同一个url信息。 149 | */ 150 | if(!self.downloaderCallBack[url]){ 151 | self.downloaderCallBack[url] = [NSMutableArray new]; 152 | firstDownload = YES; 153 | } 154 | 155 | NSMutableArray *callBacksArray = self.downloaderCallBack[url]; 156 | NSMutableDictionary *callBacks = [[NSMutableDictionary alloc] init]; 157 | if (progressBlock) { 158 | callBacks[@"progress"] = [progressBlock copy]; 159 | } 160 | if (completedBlock) { 161 | callBacks[@"completed"] = [completedBlock copy]; 162 | } 163 | [callBacksArray addObject:callBacks]; 164 | self.downloaderCallBack[url] = callBacksArray; 165 | 166 | if (firstDownload) { 167 | downloaderCreateBlock(); 168 | } 169 | }); 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /LYCache/Manage/LDSCacheManage.h: -------------------------------------------------------------------------------- 1 | // 2 | // LDCacheManage.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/5/21. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LDSImageDownloader.h" 11 | #import "LDSImageCache.h" 12 | 13 | @interface LDSCacheManage : NSObject 14 | 15 | /** 16 | * LDCacheManage的单例 17 | * 18 | * @return LDCacheManage 19 | */ 20 | +(LDSCacheManage *)shareLDCacheManage; 21 | 22 | /** 23 | * 根据URL下载图片 24 | * 25 | * @param url url地址 26 | * @param progressBlock 下载进度回调 27 | * @param completedBlock 下载完成回调 28 | */ 29 | -(void)downImageWithURL:(NSString *)url DownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /LYCache/Manage/LDSCacheManage.m: -------------------------------------------------------------------------------- 1 | // 2 | // LDCacheManage.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/5/21. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "LDSCacheManage.h" 10 | 11 | @implementation LDSCacheManage 12 | 13 | +(LDSCacheManage *)shareLDCacheManage{ 14 | static LDSCacheManage *lDSCacheManage; 15 | static dispatch_once_t onceToken; 16 | 17 | dispatch_once(&onceToken, ^{ 18 | lDSCacheManage = [[LDSCacheManage alloc] init]; 19 | }); 20 | return lDSCacheManage; 21 | } 22 | 23 | 24 | -(void)downImageWithURL:(NSString *)urlString DownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock{ 25 | NSURL *url = [NSURL URLWithString:urlString]; 26 | if ([urlString isKindOfClass:NSString.class]) { 27 | url = [NSURL URLWithString:urlString]; 28 | } 29 | if (![url isKindOfClass:NSURL.class]) { 30 | url = nil; 31 | } 32 | [[LDSImageCache shareLDImageCache] selectImageWithKey:urlString completedBlock:^(UIImage *image,NSError *error,ImageCacheType type){ 33 | 34 | if (image) { 35 | dispatch_async(dispatch_get_main_queue(), ^{ 36 | NSData *data = UIImagePNGRepresentation(image); 37 | completedBlock(data,image,error,YES); 38 | NSLog(@"读取缓存"); 39 | 40 | }); 41 | }else{ 42 | NSLog(@"进入下载"); 43 | [[LYImageDownloader shareDownloader] downloaderImageWithDownloaderWithURL:url 44 | DownloaderProgressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 45 | dispatch_async(dispatch_get_main_queue(), ^{ 46 | progressBlock(alreadyReceiveSize,expectedContentLength); 47 | }); 48 | } 49 | DownloaderCompletedBlock:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 50 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 51 | [[LDSImageCache shareLDImageCache] saveImageWithMemoryCache:nil image:image imageData:data urlKey:urlString isSaveToDisk:YES]; 52 | }); 53 | 54 | dispatch_async(dispatch_get_main_queue(), ^{ 55 | completedBlock(data,image,error,YES); 56 | 57 | }); 58 | }]; 59 | } 60 | }]; 61 | 62 | 63 | 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /LYCache/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /LYCache/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "LDSImageDownloader.h" 11 | #import "LDSImageCache.h" 12 | #import "LDSCacheManage.h" 13 | #import "UIImageView+Cache.h" 14 | 15 | @interface ViewController () 16 | @property (weak, nonatomic) IBOutlet UIImageView *image; 17 | @property (weak, nonatomic) IBOutlet UILabel *status; 18 | @property (weak, nonatomic) IBOutlet UIProgressView *progressView; 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | // Do any additional setup after loading the view, typically from a nib. 27 | 28 | } 29 | - (IBAction)read:(id)sender { 30 | 31 | [[LDSImageCache shareLDImageCache] clearDiskOnCompletion:^{ 32 | NSLog(@"完成清空"); 33 | }]; 34 | 35 | } 36 | - (IBAction)test:(id)sender { 37 | 38 | [self.image lds_setImageWithURL:@"https://sinacloud.net/keke-han/1.jpg" progressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 39 | self.progressView.progress = alreadyReceiveSize/(double)expectedContentLength; 40 | 41 | } completed:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 42 | dispatch_async(dispatch_get_main_queue(), ^{ 43 | self.status.text = @"成功"; 44 | }); 45 | }]; 46 | } 47 | 48 | - (void)didReceiveMemoryWarning { 49 | [super didReceiveMemoryWarning]; 50 | // Dispose of any resources that can be recreated. 51 | } 52 | 53 | 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /LYCache/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LYCache 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LYCacheTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LYCacheTests/LYCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LYCacheTests.m 3 | // LYCacheTests 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LYCacheTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation LYCacheTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LYCacheUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LYCacheUITests/LYCacheUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LYCacheUITests.m 3 | // LYCacheUITests 4 | // 5 | // Created by LastDays on 16/4/14. 6 | // Copyright © 2016年 LastDays. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LYCacheUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation LYCacheUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iOS图片动态缓存方案 3 | categories: 4 | - Tools 5 | --- 6 | 7 | * 大家好,我叫LastDays,大三学生,目前在腾讯实习,这是我的[Blog](lastdays.cn),我在这里分享我的学习, 8 | * 我的[微博](http://weibo.com/p/1005055848341536/home?from=page_100505&mod=TAB&is_all=1#place)我在这里分享我的生活,欢迎交流 9 | * 并且欢迎加入qq群554602551,这里我们一起交流分享 10 | 11 | 一直都想写一个关于图片缓存方案的,但是积累不够,一直扔在这,最近开始尝试。就先从我写下载开始分享。 12 | 13 | ## 下载 14 | 15 | 16 | 17 | 下载的话,我查阅了很多人写的,像SDWebImage,使用的是NSURLConnection,但是我这里准备使用NSURLsession,使用NSURLSessionDataDelegate的代理方法实现下载数据. 18 | 19 | 说点题外话:我为什么选择NSURLsession二部选择NSURLConnection。因为iOS9之前在做网络连接的时候,我们使用的时NSURLConnection,但是iOS9之后NSURLConnection宣布被弃用了,在2013年的WWDC大会上,苹果就已经设计出NSURLConnection的继任者NSURLSession,他使用起来比NSURLConnection更加简单,更加强大。 20 | 21 | 在这个过程当中,还会用到GCD与NSOperation来管理下载线程,为什么混合使用呢?我们使用子类化NSOperation来高复抽象我们的下载线程进行抽象化,这样使我们的下载模块更加清晰,在整个不算太复杂的下载过程中,让接口变得简单。GDC我们在下载中局部会使用到,GCD的优点我们都知道,简单,易用,节省代码,使用block让代码变得更加简洁。 22 | 23 | 24 | 基本上使用的东西上面都总结完了,开始进入下载的设计。 25 | 26 | 27 | 28 | ### 线程程管理器 29 | 30 | 使用子类化自定义NSOperation,这样一个下载就是一条线程,管理这些线程的话,就需要一个下载管理器,我们就是先来构建这个下载管理器。 31 | 32 | 这个管理器在整个下载模块中起到的就是对线程资源进行管理,起到一个工具的作用,这样的话我们需要把管理器构建成一个单例类,所以这里我们需要先使用单例模式来达到数据共享的目的。 33 | 34 | ``` bash 35 | +(instancetype)shareDownloader{ 36 | 37 | static LYImageDownloader *lyImageDownloader; 38 | static dispatch_once_t onceToken; 39 | dispatch_once(&onceToken, ^{ 40 | lyImageDownloader = [[LYImageDownloader alloc] init]; 41 | }); 42 | return lyImageDownloader; 43 | } 44 | 45 | ``` 46 | 47 | 以上就是我们下载管理器的单例。 48 | 49 | 50 | 整个下载的时候,通过阅读开源库,查找资料,发现很多的设计者他们的下载都具备状态监听,这个状态指的就是像下载进度,完成进度,错误信息回调。这些都是下载过程中,我们需要实时知道的东西。 51 | 52 | 这些信息都准备以block回调的形式展现,具体如下: 53 | 54 | ``` bash 55 | /** 56 | * 无参数block 57 | */ 58 | typedef void(^DownloaderCreateBlock)(); 59 | 60 | /** 61 | * 下载回调信息,下载进度Block 62 | * 63 | * @param AlreadyReceiveSize 已经接收大小 64 | * @param NotReceiveSize 未接收大小 65 | */ 66 | typedef void(^DownloaderProgressBlock)(NSInteger alreadyReceiveSize,NSInteger expectedContentLength); 67 | 68 | /** 69 | * 下载回调信息,完成下载Block 70 | * 71 | * @param data data 72 | * @param image 图片 73 | * @param error 错误信息 74 | * @param finished 是否完成 75 | */ 76 | typedef void(^DownloaderCompletedBlock)(NSData *data,UIImage *image,NSError *error,BOOL finished); 77 | 78 | ``` 79 | 80 | 在整个下载中,我们还需要有一些配置选项,例如是否允许后台下载,选择队列下载方式,还是栈的下载方式.所以设置了以下的选项。 81 | 82 | ``` bash 83 | typedef NS_OPTIONS(NSInteger,DownloaderOptions) { 84 | 85 | //默认下载操作 86 | DownloaderDefault = 1, 87 | 88 | //允许后台操作 89 | DownloaderContinueInBackground = 2 90 | }; 91 | 92 | typedef NS_ENUM(NSInteger,DownloaderOrder){ 93 | 94 | //默认下载顺序,先进先出 95 | DownloaderFIFO, 96 | 97 | //先进后出 98 | DownloaderLIFO 99 | }; 100 | ``` 101 | 102 | 基本的信息构建完成,我考虑的就是需要将这些状态的回调信息存在一个NSMutableDictionary中,key值就是我们的下载地址,value就是NSMutableArray,里面包含所DownloaderProgressBlock,DownloaderCompletedBlock进度信息。 103 | 104 | 定义了一下属性: 105 | 106 | ``` bash 107 | /** 108 | * 将所有的下载回调信息存储在这里,Key是URL,Value是多组回调信息 109 | */ 110 | @property(strong,nonatomic) NSMutableDictionary *downloaderCallBack; 111 | ``` 112 | 113 | 在一个下载开始之前,需要加载,或者是删除一些状态信息,构建了以下的函数。 114 | 115 | ``` bash 116 | /** 117 | * 添加回调信息 118 | * 119 | * @param progressBlock DownloaderProgressBlock 120 | * @param completedBlock DownloaderCompletedBlock 121 | * @param url url 122 | * @param DownloaderCreateBlock DownloaderCreateBlock 123 | */ 124 | -(void)addWithDownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock URL:(NSURL *)url DownloaderCreateBlock:(DownloaderCreateBlock)downloaderCreateBlock{ 125 | 126 | /** 127 | * 判断url是否为空 128 | */ 129 | if ([url isEqual:nil]) { 130 | completedBlock(nil,nil,nil,NO); 131 | } 132 | 133 | /** 134 | * 设置屏障,保证在同一时间,只有一个线程可以操作downloaderCallBack属性,保证在并行多个处理的时候,对downloaderCallBack属性的读写操作保持一致 135 | */ 136 | dispatch_barrier_sync(self.concurrentQueue, ^{ 137 | 138 | BOOL firstDownload = NO; 139 | /** 140 | * 添加回调信息,处理同一个url信息。 141 | */ 142 | if(!self.downloaderCallBack[url]){ 143 | self.downloaderCallBack[url] = [NSMutableArray new]; 144 | firstDownload = YES; 145 | } 146 | 147 | NSMutableArray *callBacksArray = self.downloaderCallBack[url]; 148 | NSMutableDictionary *callBacks = [[NSMutableDictionary alloc] init]; 149 | if (progressBlock) { 150 | callBacks[@"progress"] = [progressBlock copy]; 151 | } 152 | if (completedBlock) { 153 | callBacks[@"completed"] = [completedBlock copy]; 154 | } 155 | [callBacksArray addObject:callBacks]; 156 | self.downloaderCallBack[url] = callBacksArray; 157 | 158 | if (firstDownload) { 159 | downloaderCreateBlock(); 160 | } 161 | }); 162 | } 163 | 164 | ``` 165 | 166 | 首先就是判断当前的url是否为空,如果为空,直接回调空处理。不为空的话,为了防止同一URL的value被重复创建,我们需要在这里判断下原来是否存在,是否为第一次下载,是第一下下载的话,这里我们会触发adownloaderCreateBlock()的回调来进行operation的配置,当然如果不是第一次,我就仅仅需要把这个新的DownloaderProgressBlock,DownloaderCompletedBlock放进callBacksArray中即可。 167 | 168 | 这里为了保证downloaderCallBack的线程安全性,我们加了一个屏障,来保证每次只有一个线程操作downloaderCallBack属性。 169 | 170 | 171 | 这么做的一个好处就是,我每一个下载,我判断一下是不是同一URL,是的话我就做伪下载,就是感觉上下载,但是不下载,然后已经正在下载进度会同时反馈给当前其他相同的下载请求。 172 | 173 | 整个下载管理器,我们需要将下载在一个模块中被管理。就像下面这样 174 | 175 | ``` bash 176 | /** 177 | * 下载管理器对于下载请求的管理 178 | * 179 | * @param progressBlock DownloaderProgressBlock 180 | * @param completedBlock DownloaderCompletedBlock 181 | * @param url url 182 | */ 183 | -(void)downloaderImageWithDownloaderWithURL:(NSURL *)url DownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock{ 184 | 185 | __weak __typeof(self)myself = self; 186 | __block LYDownloaderOperation *operation; 187 | 188 | 189 | [self addWithDownloaderProgressBlock:progressBlock DownloaderCompletedBlock:completedBlock URL:url DownloaderCreateBlock:^{ 190 | 191 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20]; 192 | 193 | operation = [[LYDownloaderOperation alloc] initWithRequest:request 194 | DownloaderOptions:1 195 | DownloaderProgressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 196 | 197 | __block NSArray *urlCallBacks; 198 | 199 | dispatch_sync(self.concurrentQueue, ^{ 200 | urlCallBacks = [myself.downloaderCallBack[url] copy]; 201 | }); 202 | for (NSDictionary *callbacks in urlCallBacks) { 203 | dispatch_async(dispatch_get_main_queue(), ^{ 204 | DownloaderProgressBlock progress = callbacks[@"progress"]; 205 | if (progress) { 206 | progress(alreadyReceiveSize,expectedContentLength); 207 | } 208 | }); 209 | } 210 | } 211 | DownloaderCompletedBlock:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 212 | completedBlock(data,image,error,finished); 213 | } 214 | cancelled:^{ 215 | 216 | }]; 217 | [myself.downloadQueue addOperation:operation]; 218 | 219 | }]; 220 | } 221 | ``` 222 | 223 | 这部分主要就是在配置我们的operation,将配置完成后的operation添加到下载队列。 224 | 225 | ``` bash 226 | /** 227 | * 下载队列 228 | */ 229 | @property(strong,nonatomic) NSOperationQueue *downloadQueue; 230 | 231 | ``` 232 | 233 | ``` bash 234 | [myself.downloadQueue addOperation:operation]; 235 | ``` 236 | 237 | 在这里: 238 | 239 | ``` bash 240 | DownloaderProgressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 241 | 242 | __block NSArray *urlCallBacks; 243 | 244 | dispatch_sync(self.concurrentQueue, ^{ 245 | urlCallBacks = [myself.downloaderCallBack[url] copy]; 246 | }); 247 | for (NSDictionary *callbacks in urlCallBacks) { 248 | dispatch_async(dispatch_get_main_queue(), ^{ 249 | DownloaderProgressBlock progress = callbacks[@"progress"]; 250 | if (progress) { 251 | progress(alreadyReceiveSize,expectedContentLength); 252 | } 253 | }); 254 | } 255 | } 256 | ``` 257 | 258 | 就是进度的回调通知,在这里可以知道,如果我们使用了GCD,保证通知对象加载完整后在进行通知 259 | 260 | 261 | ``` bash 262 | dispatch_sync(self.concurrentQueue, ^{ 263 | urlCallBacks = [myself.downloaderCallBack[url] copy]; 264 | }); 265 | 266 | ``` 267 | 这里使用同步保证了我们进度被通知对象的完整性。 268 | 269 | 接下来的话就是异步回调通知了,下面的其他地方的基本结构也都做了类似的处理。主要就是保证每一条下载线程,每一条通知都安全的进行着。在完成下载的时候移除对应url的状态,这里也是为了保证downloaderCallBack的线程安全性,我们加了一个屏障,来保证每次只有一个线程操作downloaderCallBack属性。 270 | 271 | ### 子类化NSOperation 272 | 273 | 这里开始就是做下载处理了。需要做的就是重写start方法,在这里创建并且配置NSURLSession对象。 274 | 275 | ``` bash 276 | -(void)start{ 277 | NSLog(@"start"); 278 | /** 279 | * 创建NSURLSessionConfiguration类的对象, 这个对象被用于创建NSURLSession类的对象. 280 | */ 281 | NSURLSessionConfiguration *configura = [NSURLSessionConfiguration defaultSessionConfiguration]; 282 | 283 | /** 284 | * 2. 创建NSURLSession的对象. 285 | * 参数一 : NSURLSessionConfiguration类的对象.(第1步创建的对象.) 286 | * 参数二 : session的代理人. 如果为nil, 系统将会提供一个代理人. 287 | * 参数三 : 一个队列, 代理方法在这个队列中执行. 如果为nil, 系统会自动创建一系列的队列. 288 | * 注: 只能通过这个方法给session设置代理人, 因为在NSURLSession中delegate属性是只读的. 289 | */ 290 | NSURLSession *session = [NSURLSession sessionWithConfiguration:configura delegate:self delegateQueue:nil]; 291 | 292 | /** 293 | * 创建request 294 | */ 295 | NSMutableURLRequest *request = self.request; 296 | 297 | /** 298 | * 创建数据类型任务 299 | */ 300 | NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; 301 | 302 | /** 303 | * 开始任务 304 | */ 305 | [dataTask resume]; 306 | 307 | /** 308 | * 在session中的所有任务都完成之后, 使session失效. 309 | */ 310 | [session finishTasksAndInvalidate]; 311 | 312 | } 313 | ``` 314 | 315 | 因为我们实现了NSURLSessionDataDelegate协议,所以可以自定义一些操作。 316 | 317 | ``` bash 318 | //最先调用,在这里做一些数据的初始化。 319 | -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{ 320 | NSLog(@"开始"); 321 | self.imageData = [[NSMutableData alloc] init]; 322 | self.expectedContentLength = response.expectedContentLength; 323 | 324 | if (self.progressBlock) { 325 | self.progressBlock(0,self.expectedContentLength); 326 | } 327 | 328 | completionHandler(NSURLSessionResponseAllow); 329 | 330 | } 331 | 332 | //下载响应 333 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 334 | didReceiveData:(NSData *)data{ 335 | 336 | [self.imageData appendData:data]; 337 | if (self.progressBlock) { 338 | self.progressBlock(self.imageData.length,self.expectedContentLength); 339 | } 340 | 341 | 342 | } 343 | 344 | //下载完成后调用 345 | -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ 346 | 347 | self.completedBlock(self.imageData,nil,error,YES); 348 | [self cancel]; 349 | } 350 | 351 | ``` 352 | 353 | 重写了以上的一些实现。 354 | 355 | 最后强调下这里: 356 | 357 | ``` bash 358 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30]; 359 | ``` 360 | 为了避免潜在的重复缓存,NSURLCache与自己的缓存方案,则禁用图片请求的缓存操作。 361 | 362 | 基本上我们就可以进行测试了: 363 | 364 | 写了一个简单UI,用来展示下载部分 365 | 366 | ![](image/cache/3.gif) 367 | 368 | 369 | ## 缓存 370 | 371 | 缓存这个地方需要考虑的东西还是很多的,那么将会针对一下问题进行描述,设计。 372 | 373 | * 缓存在内存,或者是磁盘,或者是内存+磁盘 374 | * 缓存是否成功 375 | * 缓存的容器 376 | * 存储图片方式 377 | * 查询图片 378 | * 清理图片(完全清理,清理部分,其中清理部分图片的清理方式为按时间,安空间的大小) 379 | 380 | 直接上代码: 381 | 382 | ``` bash 383 | /** 384 | * 进行缓存 385 | * 386 | * @param memoryCache 内存 387 | * @param image 图片 388 | * @param imageData 图片data 389 | * @param urlKey key值就用来唯一标记数据 390 | * @param isSaveTOdisk 是否进行沙箱缓存 391 | */ 392 | -(void)saveImageWithMemoryCache:(NSCache *)memoryCache image:(UIImage *)image imageData:(NSData *)imageData urlKey:(NSString *)urlKey isSaveToDisk:(BOOL)isSaveToDisk{ 393 | 394 | //内存缓存 395 | if ([memoryCache isEqual:nil]) { 396 | [self.memoryCache setObject:image forKey:urlKey]; 397 | 398 | }else{ 399 | [memoryCache setObject:image forKey:urlKey]; 400 | } 401 | 402 | //磁盘缓存 403 | if (isSaveToDisk) { 404 | dispatch_sync(self.ioSerialQueue, ^{ 405 | if (![_fileMange fileExistsAtPath:_diskCachePath]) { 406 | [_fileMange createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:nil]; 407 | } 408 | NSString *pathForKey = [self defaultCachePathForKey:urlKey]; 409 | 410 | NSLog(@"%@",pathForKey); 411 | 412 | [_fileMange createFileAtPath:pathForKey contents:imageData attributes:nil]; 413 | }); 414 | } 415 | } 416 | 417 | ``` 418 | 419 | 注释中基本上就描述了各部分的职责,首先就是内存缓存,这里的内存缓存我用NSCache进行处理,这里用NSCache就是其实就是一个集合类型,这个集合类型维护这一个key-value结构。当某一些对象销毁的代价,或者重新生成的代价高于我们内存保留。那么我们就把它内存缓存下来就是很值的。因此重用这些对象是很值得的,毕竟我们不需要二次计算了,并且当我们的内存警报的时候,他自己会丢弃掉一些没用的东西的。 420 | 就像代码中标记的,就是做了内存缓存工作。 421 | 422 | 423 | 磁盘缓存使用NSFileManager实现,存放的位置就是沙箱的Cache文件夹内,这样就可以了。并且我们可以看到,这里我们是可以根据isSaveToDisk来判断是否需要进行磁盘的缓存,因为有一些东西是不需要缓存在磁盘中的,另外,异步操作也是很关键的一个地方,同样我们在这里使用dispatch_sync来做一些处理,实现我们的异步操作。并且这里的文件名实使用是将URL变换为MD5值。保证了唯一性。 424 | 425 | 426 | 缓存的操作基本上就完成了,既然能存,就需要对应查询。 427 | 428 | ``` bash 429 | //查询图片 430 | -(void)selectImageWithKey:(NSString *)urlKey completedBlock:(CompletedBlock)completed{ 431 | UIImage *image = [self.memoryCache objectForKey:urlKey]; 432 | if ([image isEqual:nil]) { 433 | NSLog(@"ok"); 434 | completed(image,nil,ImageCacheTypeMemory); 435 | 436 | }else{ 437 | 438 | NSString *pathForKey = [self defaultCachePathForKey:urlKey]; 439 | 440 | NSLog(@"%@",pathForKey); 441 | 442 | NSData *imageData = [NSData dataWithContentsOfFile:pathForKey]; 443 | UIImage *diskImage = [UIImage imageWithData:imageData]; 444 | completed(diskImage,nil,ImageCacheTypeDisk); 445 | } 446 | } 447 | ``` 448 | 449 | 这里的查询基本上就是两种方式,第一种如果内存中存在,那么就在内存中读取就可以了。当然也存在着内存中不存在的可能性,这样就需要从磁盘中开始读取信息数据。根据MD5值进行索引,然后block回调给上层数据信息进行处理。 450 | 451 | 452 | 最后就是删除操作,因为如果我们设置了磁盘的上限,当我们设定的磁盘空间达到上限的时候该怎么做?当我们想清空所有缓存的时候,我们该怎么做呢?下面的这两段代码就是为了做清理磁盘空间的事情的。 453 | 454 | ``` bash 455 | /** 456 | * 清空全部 457 | * 458 | * @param completion completion 459 | */ 460 | - (void)clearDiskOnCompletion:(NoParamsBlock)completion 461 | { 462 | dispatch_async(self.ioSerialQueue, ^{ 463 | [_fileMange removeItemAtPath:self.diskCachePath error:nil]; 464 | [_fileMange createDirectoryAtPath:self.diskCachePath 465 | withIntermediateDirectories:YES 466 | attributes:nil 467 | error:NULL]; 468 | if (completion) { 469 | dispatch_async(dispatch_get_main_queue(), ^{ 470 | completion(); 471 | }); 472 | } 473 | }); 474 | } 475 | ``` 476 | 477 | 这段代码的作用就是为了做清空磁盘的作用,同样是使用NSFileManager来实现。 478 | 479 | 480 | ``` bash 481 | /** 482 | * 按条件进行清空(主要是时间),这里盗用了SDWebImage的设计 483 | * 484 | * @param noParamsBlock completion 485 | */ 486 | -(void)clearDiskWithNoParamsBlock:(NoParamsBlock)noParamsBlock{ 487 | 488 | dispatch_async(self.ioSerialQueue, ^{ 489 | 490 | NSURL *diskCache = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; 491 | NSArray *resourcKeys = @[NSURLIsDirectoryKey,NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; 492 | 493 | 494 | 495 | // 1. 该枚举器预先获取缓存文件的有用的属性 496 | NSDirectoryEnumerator *fileEnumerator = [_fileMange enumeratorAtURL:diskCache 497 | includingPropertiesForKeys:resourcKeys 498 | options:NSDirectoryEnumerationSkipsHiddenFiles 499 | errorHandler:NULL]; 500 | 501 | NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-60 * 60 * 24 * 7]; 502 | NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary]; 503 | NSInteger currentCacheSize = 0; 504 | 505 | NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; 506 | 507 | for (NSURL *fileURL in fileEnumerator) { 508 | NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourcKeys error:NULL]; 509 | 510 | 511 | // 3. 跳过文件夹 512 | if ([resourceValues[NSURLIsDirectoryKey] boolValue]) { 513 | continue; 514 | } 515 | 516 | NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; 517 | if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { 518 | [urlsToDelete addObject:fileURL]; 519 | 520 | continue; 521 | } 522 | 523 | 524 | // 5. 存储文件的引用并计算所有文件的总大小,以备后用 525 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 526 | currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; 527 | [cacheFiles setObject:resourceValues forKey:fileURL]; 528 | 529 | } 530 | 531 | for (NSURL *fileURL in urlsToDelete) { 532 | [self.fileMange removeItemAtURL:fileURL error:NULL]; 533 | } 534 | 535 | if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) { 536 | const NSUInteger desiredCacheSize = self.maxCacheSize / 2; 537 | 538 | // Sort the remaining cache files by their last modification time (oldest first). 539 | NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent 540 | usingComparator:^NSComparisonResult(id obj1, id obj2) { 541 | return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; 542 | }]; 543 | 544 | // Delete files until we fall below our desired cache size. 545 | for (NSURL *fileURL in sortedFiles) { 546 | if ([_fileMange removeItemAtURL:fileURL error:nil]) { 547 | NSDictionary *resourceValues = cacheFiles[fileURL]; 548 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 549 | currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; 550 | 551 | if (currentCacheSize < desiredCacheSize) { 552 | break; 553 | } 554 | } 555 | } 556 | } 557 | if (noParamsBlock) { 558 | dispatch_async(dispatch_get_main_queue(), ^{ 559 | noParamsBlock(); 560 | }); 561 | } 562 | 563 | }); 564 | 565 | } 566 | 567 | ``` 568 | 569 | 这段代码就是实现了部分的清理工作,清理的工作就是根据我们设定的一些参数来实现的,这里包含着我们设定的缓存有效期,缓存的最大空间是多少。过了我们设定的有效期,这个时候我们就需要去清理掉这部分内容。另外如果如果所有缓存文件的总大小超过这一大小,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间 570 | 571 | 到了这里我们的缓存的方案就基本上完成了,我们做一个小小的测试,从本地读取一个文件,然后缓存到cache文件加重,然后进行加载,看一下效果。来看一下测试代码: 572 | 573 | 574 | ``` bash 575 | - (IBAction)read:(id)sender { 576 | 577 | LDImageCache *l = [LDImageCache shareLDImageCache]; 578 | 579 | UIImage *myImage = [UIImage imageNamed:@"author.jpg"]; 580 | NSData *data = UIImagePNGRepresentation(myImage); 581 | [l saveImageWithMemoryCache:nil image:myImage imageData:data urlKey:@"lastdays.cn" isSaveToDisk:YES]; 582 | 583 | 584 | 585 | [l selectImageWithKey:@"lastdays.cn" 586 | completedBlock:^(UIImage *image,NSError *error,ImageCacheType type){ 587 | NSLog(@"%ld",(long)type); 588 | [self.image setImage:image]; 589 | }]; 590 | 591 | 592 | // [l clearDiskOnCompletion:^{ 593 | // NSLog(@"完成清空"); 594 | // }]; 595 | 596 | } 597 | ``` 598 | 599 | 这里的读取数据,首先我们从本地读取一个图片,然后调用saveImageWithMemoryCache:将图片数据缓存在内存和磁盘中。然后根据key值调用selectImageWithKey进行查询,这里我们输出的ImageCacheType数据,查看一下都是从哪里进行的读取。我们会优先从内存中进行读取数据。 600 | 601 | 602 | ## 管理器 603 | 604 | 实际的应用场景下我们不可能去直接使用下载跟缓存来处理完成我们的图片混存的,所以需要提供一个管理器去帮助我们直接的去完成图片的下载以及缓存工作。所以在这里我们设计一个LDSCacheManage,就是将下载和管理绑定在一起。方便上层使用。 605 | 606 | 看下具体代码: 607 | 608 | 609 | ``` bash 610 | 611 | -(void)downImageWithURL:(NSString *)urlString DownloaderProgressBlock:(DownloaderProgressBlock)progressBlock DownloaderCompletedBlock:(DownloaderCompletedBlock)completedBlock{ 612 | NSURL *url = [NSURL URLWithString:urlString]; 613 | if ([urlString isKindOfClass:NSString.class]) { 614 | url = [NSURL URLWithString:urlString]; 615 | } 616 | if (![url isKindOfClass:NSURL.class]) { 617 | url = nil; 618 | } 619 | [[LDSImageCache shareLDImageCache] selectImageWithKey:urlString completedBlock:^(UIImage *image,NSError *error,ImageCacheType type){ 620 | 621 | if (image) { 622 | dispatch_async(dispatch_get_main_queue(), ^{ 623 | NSData *data = UIImagePNGRepresentation(image); 624 | completedBlock(data,image,error,YES); 625 | NSLog(@"读取缓存"); 626 | 627 | }); 628 | }else{ 629 | NSLog(@"进入下载"); 630 | [[LYImageDownloader shareDownloader] downloaderImageWithDownloaderWithURL:url 631 | DownloaderProgressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 632 | dispatch_async(dispatch_get_main_queue(), ^{ 633 | progressBlock(alreadyReceiveSize,expectedContentLength); 634 | }); 635 | } 636 | DownloaderCompletedBlock:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 637 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 638 | [[LDSImageCache shareLDImageCache] saveImageWithMemoryCache:nil image:image imageData:data urlKey:urlString isSaveToDisk:YES]; 639 | }); 640 | 641 | dispatch_async(dispatch_get_main_queue(), ^{ 642 | completedBlock(data,image,error,YES); 643 | 644 | }); 645 | }]; 646 | } 647 | }]; 648 | 649 | ``` 650 | 651 | 可以看到,我们先是对URL的正确性进行处理转化,然后我们是根据URl先从磁盘中读取缓存的,如果磁盘中存在,直接回调image就可以了,如果没有就进入下载,这里就是调用我们的downloaderImageWithDownloaderWithURL进入下载,下载完成后我们需要做缓存 652 | 653 | ``` 654 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 655 | [[LDSImageCache shareLDImageCache] saveImageWithMemoryCache:nil image:image imageData:data urlKey:urlString isSaveToDisk:YES]; 656 | }); 657 | ``` 658 | 659 | 这里我们采用的是在一个全局队列中并行处理一个缓存操作。然后回调数据。 660 | 661 | ``` 662 | +(LDSCacheManage *)shareLDCacheManage{ 663 | static LDSCacheManage *lDSCacheManage; 664 | static dispatch_once_t onceToken; 665 | 666 | dispatch_once(&onceToken, ^{ 667 | lDSCacheManage = [[LDSCacheManage alloc] init]; 668 | }); 669 | return lDSCacheManage; 670 | } 671 | 672 | 673 | ``` 674 | 675 | 在这里我们也是提供了一个单例。 676 | 677 | 678 | ## 视图扩展 679 | 680 | 最后就是对UIImageView做视图扩展,使其拥有异步下载和缓存远程图片的能力。 681 | 682 | ``` bash 683 | - (void)lds_setImageWithURL:(NSString *)url progressBlock:(DownloaderProgressBlock)progressBlock completed:(DownloaderCompletedBlock)completedBlock{ 684 | __weak __typeof(self)wself = self; 685 | [[LDSCacheManage shareLDCacheManage] downImageWithURL:url 686 | DownloaderProgressBlock:^(NSInteger aleradyReceiveSize,NSInteger expectedContentLength){ 687 | if (progressBlock) { 688 | progressBlock(aleradyReceiveSize,expectedContentLength); 689 | } 690 | 691 | }DownloaderCompletedBlock:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 692 | dispatch_async(dispatch_get_main_queue(), ^{ 693 | wself.image = image; 694 | [wself setNeedsLayout]; 695 | if (completedBlock) { 696 | completedBlock(data, image, error, YES); 697 | } 698 | }); 699 | 700 | }]; 701 | } 702 | 703 | 704 | ``` 705 | 706 | 调用LDSCacheManage,进行图片的读取,至于读取缓存内容还是去下载啊,那就交给LDSCacheManage去处理好了,我们就不管了。调用[wself setNeedsLayout]去更新视图就可以了。 707 | 708 | ## 使用 709 | 710 | ``` bash 711 | - (IBAction)read:(id)sender { 712 | 713 | [[LDSImageCache shareLDImageCache] clearDiskOnCompletion:^{ 714 | NSLog(@"完成清空"); 715 | }]; 716 | 717 | } 718 | - (IBAction)test:(id)sender { 719 | 720 | [self.image lds_setImageWithURL:@"https://sinacloud.net/keke-han/1.jpg" progressBlock:^(NSInteger alreadyReceiveSize,NSInteger expectedContentLength){ 721 | self.progressView.progress = alreadyReceiveSize/(double)expectedContentLength; 722 | 723 | } completed:^(NSData *data,UIImage *image,NSError *error,BOOL finished){ 724 | dispatch_async(dispatch_get_main_queue(), ^{ 725 | self.status.text = @"成功"; 726 | }); 727 | }]; 728 | } 729 | 730 | ``` 731 | 732 | 看下效果: 733 | 734 | ![](image/3.gif) 735 | 736 | 737 | ## 总结 738 | 739 | * 缓存主要注意清空策略 740 | * 下载主要还是对操作的管理 741 | * 网络请求应用的是NSURLSession 742 | 743 | 还有很多的不足和需要改进的地方。希望大家能帮我指出。 -------------------------------------------------------------------------------- /image/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrLoong/LDSImageCache/8df1fec0d7ed587778fab1857aa03e9faf5ba86d/image/3.gif -------------------------------------------------------------------------------- /image/cache/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrLoong/LDSImageCache/8df1fec0d7ed587778fab1857aa03e9faf5ba86d/image/cache/3.gif --------------------------------------------------------------------------------