SDImageCache.m 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "SDImageCache.h"
  9. #import "NSImage+Compatibility.h"
  10. #import "SDImageCodersManager.h"
  11. #import "SDImageCoderHelper.h"
  12. #import "SDAnimatedImage.h"
  13. #import "UIImage+MemoryCacheCost.h"
  14. #import "UIImage+Metadata.h"
  15. #import "UIImage+ExtendedCacheData.h"
  16. static NSString * _defaultDiskCacheDirectory;
  17. @interface SDImageCache ()
  18. #pragma mark - Properties
  19. @property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache;
  20. @property (nonatomic, strong, readwrite, nonnull) id<SDDiskCache> diskCache;
  21. @property (nonatomic, copy, readwrite, nonnull) SDImageCacheConfig *config;
  22. @property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;
  23. @property (nonatomic, strong, nullable) dispatch_queue_t ioQueue;
  24. @end
  25. @implementation SDImageCache
  26. #pragma mark - Singleton, init, dealloc
  27. + (nonnull instancetype)sharedImageCache {
  28. static dispatch_once_t once;
  29. static id instance;
  30. dispatch_once(&once, ^{
  31. instance = [self new];
  32. });
  33. return instance;
  34. }
  35. + (NSString *)defaultDiskCacheDirectory {
  36. if (!_defaultDiskCacheDirectory) {
  37. _defaultDiskCacheDirectory = [[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"];
  38. }
  39. return _defaultDiskCacheDirectory;
  40. }
  41. + (void)setDefaultDiskCacheDirectory:(NSString *)defaultDiskCacheDirectory {
  42. _defaultDiskCacheDirectory = [defaultDiskCacheDirectory copy];
  43. }
  44. - (instancetype)init {
  45. return [self initWithNamespace:@"default"];
  46. }
  47. - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
  48. return [self initWithNamespace:ns diskCacheDirectory:nil];
  49. }
  50. - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
  51. diskCacheDirectory:(nullable NSString *)directory {
  52. return [self initWithNamespace:ns diskCacheDirectory:directory config:SDImageCacheConfig.defaultCacheConfig];
  53. }
  54. - (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
  55. diskCacheDirectory:(nullable NSString *)directory
  56. config:(nullable SDImageCacheConfig *)config {
  57. if ((self = [super init])) {
  58. NSAssert(ns, @"Cache namespace should not be nil");
  59. // Create IO serial queue
  60. _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL);
  61. if (!config) {
  62. config = SDImageCacheConfig.defaultCacheConfig;
  63. }
  64. _config = [config copy];
  65. // Init the memory cache
  66. NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)], @"Custom memory cache class must conform to `SDMemoryCache` protocol");
  67. _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];
  68. // Init the disk cache
  69. if (!directory) {
  70. // Use default disk cache directory
  71. directory = [self.class defaultDiskCacheDirectory];
  72. }
  73. _diskCachePath = [directory stringByAppendingPathComponent:ns];
  74. NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)], @"Custom disk cache class must conform to `SDDiskCache` protocol");
  75. _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
  76. // Check and migrate disk cache directory if need
  77. [self migrateDiskCacheDirectory];
  78. #if SD_UIKIT
  79. // Subscribe to app events
  80. [[NSNotificationCenter defaultCenter] addObserver:self
  81. selector:@selector(applicationWillTerminate:)
  82. name:UIApplicationWillTerminateNotification
  83. object:nil];
  84. [[NSNotificationCenter defaultCenter] addObserver:self
  85. selector:@selector(applicationDidEnterBackground:)
  86. name:UIApplicationDidEnterBackgroundNotification
  87. object:nil];
  88. #endif
  89. #if SD_MAC
  90. [[NSNotificationCenter defaultCenter] addObserver:self
  91. selector:@selector(applicationWillTerminate:)
  92. name:NSApplicationWillTerminateNotification
  93. object:nil];
  94. #endif
  95. }
  96. return self;
  97. }
  98. - (void)dealloc {
  99. [[NSNotificationCenter defaultCenter] removeObserver:self];
  100. }
  101. #pragma mark - Cache paths
  102. - (nullable NSString *)cachePathForKey:(nullable NSString *)key {
  103. if (!key) {
  104. return nil;
  105. }
  106. return [self.diskCache cachePathForKey:key];
  107. }
  108. + (nullable NSString *)userCacheDirectory {
  109. NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  110. return paths.firstObject;
  111. }
  112. - (void)migrateDiskCacheDirectory {
  113. if ([self.diskCache isKindOfClass:[SDDiskCache class]]) {
  114. static dispatch_once_t onceToken;
  115. dispatch_once(&onceToken, ^{
  116. // ~/Library/Caches/com.hackemist.SDImageCache/default/
  117. NSString *newDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
  118. // ~/Library/Caches/default/com.hackemist.SDWebImageCache.default/
  119. NSString *oldDefaultPath = [[[self.class userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
  120. dispatch_async(self.ioQueue, ^{
  121. [((SDDiskCache *)self.diskCache) moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
  122. });
  123. });
  124. }
  125. }
  126. #pragma mark - Store Ops
  127. - (void)storeImage:(nullable UIImage *)image
  128. forKey:(nullable NSString *)key
  129. completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  130. [self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
  131. }
  132. - (void)storeImage:(nullable UIImage *)image
  133. forKey:(nullable NSString *)key
  134. toDisk:(BOOL)toDisk
  135. completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  136. [self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
  137. }
  138. - (void)storeImage:(nullable UIImage *)image
  139. imageData:(nullable NSData *)imageData
  140. forKey:(nullable NSString *)key
  141. toDisk:(BOOL)toDisk
  142. completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  143. return [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:toDisk completion:completionBlock];
  144. }
  145. - (void)storeImage:(nullable UIImage *)image
  146. imageData:(nullable NSData *)imageData
  147. forKey:(nullable NSString *)key
  148. toMemory:(BOOL)toMemory
  149. toDisk:(BOOL)toDisk
  150. completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  151. if (!image || !key) {
  152. if (completionBlock) {
  153. completionBlock();
  154. }
  155. return;
  156. }
  157. // if memory cache is enabled
  158. if (toMemory && self.config.shouldCacheImagesInMemory) {
  159. NSUInteger cost = image.sd_memoryCost;
  160. [self.memoryCache setObject:image forKey:key cost:cost];
  161. }
  162. if (toDisk) {
  163. dispatch_async(self.ioQueue, ^{
  164. @autoreleasepool {
  165. NSData *data = imageData;
  166. if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
  167. // If image is custom animated image class, prefer its original animated data
  168. data = [((id<SDAnimatedImage>)image) animatedImageData];
  169. }
  170. if (!data && image) {
  171. // Check image's associated image format, may return .undefined
  172. SDImageFormat format = image.sd_imageFormat;
  173. if (format == SDImageFormatUndefined) {
  174. // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
  175. if (image.sd_isAnimated) {
  176. format = SDImageFormatGIF;
  177. } else {
  178. // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
  179. if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
  180. format = SDImageFormatPNG;
  181. } else {
  182. format = SDImageFormatJPEG;
  183. }
  184. }
  185. }
  186. data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
  187. }
  188. [self _storeImageDataToDisk:data forKey:key];
  189. if (image) {
  190. // Check extended data
  191. id extendedObject = image.sd_extendedObject;
  192. if ([extendedObject conformsToProtocol:@protocol(NSCoding)]) {
  193. NSData *extendedData;
  194. if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
  195. NSError *error;
  196. extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error];
  197. if (error) {
  198. NSLog(@"NSKeyedArchiver archive failed with error: %@", error);
  199. }
  200. } else {
  201. @try {
  202. #pragma clang diagnostic push
  203. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  204. extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject];
  205. #pragma clang diagnostic pop
  206. } @catch (NSException *exception) {
  207. NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception);
  208. }
  209. }
  210. if (extendedData) {
  211. [self.diskCache setExtendedData:extendedData forKey:key];
  212. }
  213. }
  214. }
  215. }
  216. if (completionBlock) {
  217. dispatch_async(dispatch_get_main_queue(), ^{
  218. completionBlock();
  219. });
  220. }
  221. });
  222. } else {
  223. if (completionBlock) {
  224. completionBlock();
  225. }
  226. }
  227. }
  228. - (void)storeImageToMemory:(UIImage *)image forKey:(NSString *)key {
  229. if (!image || !key) {
  230. return;
  231. }
  232. NSUInteger cost = image.sd_memoryCost;
  233. [self.memoryCache setObject:image forKey:key cost:cost];
  234. }
  235. - (void)storeImageDataToDisk:(nullable NSData *)imageData
  236. forKey:(nullable NSString *)key {
  237. if (!imageData || !key) {
  238. return;
  239. }
  240. dispatch_sync(self.ioQueue, ^{
  241. [self _storeImageDataToDisk:imageData forKey:key];
  242. });
  243. }
  244. // Make sure to call from io queue by caller
  245. - (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
  246. if (!imageData || !key) {
  247. return;
  248. }
  249. [self.diskCache setData:imageData forKey:key];
  250. }
  251. #pragma mark - Query and Retrieve Ops
  252. - (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDImageCacheCheckCompletionBlock)completionBlock {
  253. dispatch_async(self.ioQueue, ^{
  254. BOOL exists = [self _diskImageDataExistsWithKey:key];
  255. if (completionBlock) {
  256. dispatch_async(dispatch_get_main_queue(), ^{
  257. completionBlock(exists);
  258. });
  259. }
  260. });
  261. }
  262. - (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key {
  263. if (!key) {
  264. return NO;
  265. }
  266. __block BOOL exists = NO;
  267. dispatch_sync(self.ioQueue, ^{
  268. exists = [self _diskImageDataExistsWithKey:key];
  269. });
  270. return exists;
  271. }
  272. // Make sure to call from io queue by caller
  273. - (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
  274. if (!key) {
  275. return NO;
  276. }
  277. return [self.diskCache containsDataForKey:key];
  278. }
  279. - (void)diskImageDataQueryForKey:(NSString *)key completion:(SDImageCacheQueryDataCompletionBlock)completionBlock {
  280. dispatch_async(self.ioQueue, ^{
  281. NSData *imageData = [self diskImageDataBySearchingAllPathsForKey:key];
  282. if (completionBlock) {
  283. dispatch_async(dispatch_get_main_queue(), ^{
  284. completionBlock(imageData);
  285. });
  286. }
  287. });
  288. }
  289. - (nullable NSData *)diskImageDataForKey:(nullable NSString *)key {
  290. if (!key) {
  291. return nil;
  292. }
  293. __block NSData *imageData = nil;
  294. dispatch_sync(self.ioQueue, ^{
  295. imageData = [self diskImageDataBySearchingAllPathsForKey:key];
  296. });
  297. return imageData;
  298. }
  299. - (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
  300. return [self.memoryCache objectForKey:key];
  301. }
  302. - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
  303. return [self imageFromDiskCacheForKey:key options:0 context:nil];
  304. }
  305. - (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
  306. NSData *data = [self diskImageDataForKey:key];
  307. UIImage *diskImage = [self diskImageForKey:key data:data options:options context:context];
  308. if (diskImage && self.config.shouldCacheImagesInMemory) {
  309. NSUInteger cost = diskImage.sd_memoryCost;
  310. [self.memoryCache setObject:diskImage forKey:key cost:cost];
  311. }
  312. return diskImage;
  313. }
  314. - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
  315. return [self imageFromCacheForKey:key options:0 context:nil];
  316. }
  317. - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
  318. // First check the in-memory cache...
  319. UIImage *image = [self imageFromMemoryCacheForKey:key];
  320. if (image) {
  321. return image;
  322. }
  323. // Second check the disk cache...
  324. image = [self imageFromDiskCacheForKey:key options:options context:context];
  325. return image;
  326. }
  327. - (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
  328. if (!key) {
  329. return nil;
  330. }
  331. NSData *data = [self.diskCache dataForKey:key];
  332. if (data) {
  333. return data;
  334. }
  335. // Addtional cache path for custom pre-load cache
  336. if (self.additionalCachePathBlock) {
  337. NSString *filePath = self.additionalCachePathBlock(key);
  338. if (filePath) {
  339. data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
  340. }
  341. }
  342. return data;
  343. }
  344. - (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
  345. NSData *data = [self diskImageDataForKey:key];
  346. return [self diskImageForKey:key data:data];
  347. }
  348. - (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data {
  349. return [self diskImageForKey:key data:data options:0 context:nil];
  350. }
  351. - (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
  352. if (data) {
  353. UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
  354. if (image) {
  355. // Check extended data
  356. NSData *extendedData = [self.diskCache extendedDataForKey:key];
  357. if (extendedData) {
  358. id extendedObject;
  359. if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
  360. NSError *error;
  361. NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error];
  362. unarchiver.requiresSecureCoding = NO;
  363. extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
  364. if (error) {
  365. NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error);
  366. }
  367. } else {
  368. @try {
  369. #pragma clang diagnostic push
  370. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  371. extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData];
  372. #pragma clang diagnostic pop
  373. } @catch (NSException *exception) {
  374. NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception);
  375. }
  376. }
  377. image.sd_extendedObject = extendedObject;
  378. }
  379. }
  380. return image;
  381. } else {
  382. return nil;
  383. }
  384. }
  385. - (nullable NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDImageCacheQueryCompletionBlock)doneBlock {
  386. return [self queryCacheOperationForKey:key options:0 done:doneBlock];
  387. }
  388. - (nullable NSOperation *)queryCacheOperationForKey:(NSString *)key options:(SDImageCacheOptions)options done:(SDImageCacheQueryCompletionBlock)doneBlock {
  389. return [self queryCacheOperationForKey:key options:options context:nil done:doneBlock];
  390. }
  391. - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
  392. return [self queryCacheOperationForKey:key options:options context:context cacheType:SDImageCacheTypeAll done:doneBlock];
  393. }
  394. - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
  395. if (!key) {
  396. if (doneBlock) {
  397. doneBlock(nil, nil, SDImageCacheTypeNone);
  398. }
  399. return nil;
  400. }
  401. // Invalid cache type
  402. if (queryCacheType == SDImageCacheTypeNone) {
  403. if (doneBlock) {
  404. doneBlock(nil, nil, SDImageCacheTypeNone);
  405. }
  406. return nil;
  407. }
  408. // First check the in-memory cache...
  409. UIImage *image;
  410. if (queryCacheType != SDImageCacheTypeDisk) {
  411. image = [self imageFromMemoryCacheForKey:key];
  412. }
  413. if (image) {
  414. if (options & SDImageCacheDecodeFirstFrameOnly) {
  415. // Ensure static image
  416. Class animatedImageClass = image.class;
  417. if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
  418. #if SD_MAC
  419. image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
  420. #else
  421. image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
  422. #endif
  423. }
  424. } else if (options & SDImageCacheMatchAnimatedImageClass) {
  425. // Check image class matching
  426. Class animatedImageClass = image.class;
  427. Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
  428. if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
  429. image = nil;
  430. }
  431. }
  432. }
  433. BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
  434. if (shouldQueryMemoryOnly) {
  435. if (doneBlock) {
  436. doneBlock(image, nil, SDImageCacheTypeMemory);
  437. }
  438. return nil;
  439. }
  440. // Second check the disk cache...
  441. NSOperation *operation = [NSOperation new];
  442. // Check whether we need to synchronously query disk
  443. // 1. in-memory cache hit & memoryDataSync
  444. // 2. in-memory cache miss & diskDataSync
  445. BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
  446. (!image && options & SDImageCacheQueryDiskDataSync));
  447. void(^queryDiskBlock)(void) = ^{
  448. if (operation.isCancelled) {
  449. if (doneBlock) {
  450. doneBlock(nil, nil, SDImageCacheTypeNone);
  451. }
  452. return;
  453. }
  454. @autoreleasepool {
  455. NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
  456. UIImage *diskImage;
  457. if (image) {
  458. // the image is from in-memory cache, but need image data
  459. diskImage = image;
  460. } else if (diskData) {
  461. BOOL shouldCacheToMomery = YES;
  462. if (context[SDWebImageContextStoreCacheType]) {
  463. SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];
  464. shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
  465. }
  466. // decode image data only if in-memory cache missed
  467. diskImage = [self diskImageForKey:key data:diskData options:options context:context];
  468. if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {
  469. NSUInteger cost = diskImage.sd_memoryCost;
  470. [self.memoryCache setObject:diskImage forKey:key cost:cost];
  471. }
  472. }
  473. if (doneBlock) {
  474. if (shouldQueryDiskSync) {
  475. doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
  476. } else {
  477. dispatch_async(dispatch_get_main_queue(), ^{
  478. doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
  479. });
  480. }
  481. }
  482. }
  483. };
  484. // Query in ioQueue to keep IO-safe
  485. if (shouldQueryDiskSync) {
  486. dispatch_sync(self.ioQueue, queryDiskBlock);
  487. } else {
  488. dispatch_async(self.ioQueue, queryDiskBlock);
  489. }
  490. return operation;
  491. }
  492. #pragma mark - Remove Ops
  493. - (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
  494. [self removeImageForKey:key fromDisk:YES withCompletion:completion];
  495. }
  496. - (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
  497. [self removeImageForKey:key fromMemory:YES fromDisk:fromDisk withCompletion:completion];
  498. }
  499. - (void)removeImageForKey:(nullable NSString *)key fromMemory:(BOOL)fromMemory fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
  500. if (key == nil) {
  501. return;
  502. }
  503. if (fromMemory && self.config.shouldCacheImagesInMemory) {
  504. [self.memoryCache removeObjectForKey:key];
  505. }
  506. if (fromDisk) {
  507. dispatch_async(self.ioQueue, ^{
  508. [self.diskCache removeDataForKey:key];
  509. if (completion) {
  510. dispatch_async(dispatch_get_main_queue(), ^{
  511. completion();
  512. });
  513. }
  514. });
  515. } else if (completion) {
  516. completion();
  517. }
  518. }
  519. - (void)removeImageFromMemoryForKey:(NSString *)key {
  520. if (!key) {
  521. return;
  522. }
  523. [self.memoryCache removeObjectForKey:key];
  524. }
  525. - (void)removeImageFromDiskForKey:(NSString *)key {
  526. if (!key) {
  527. return;
  528. }
  529. dispatch_sync(self.ioQueue, ^{
  530. [self _removeImageFromDiskForKey:key];
  531. });
  532. }
  533. // Make sure to call from io queue by caller
  534. - (void)_removeImageFromDiskForKey:(NSString *)key {
  535. if (!key) {
  536. return;
  537. }
  538. [self.diskCache removeDataForKey:key];
  539. }
  540. #pragma mark - Cache clean Ops
  541. - (void)clearMemory {
  542. [self.memoryCache removeAllObjects];
  543. }
  544. - (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
  545. dispatch_async(self.ioQueue, ^{
  546. [self.diskCache removeAllData];
  547. if (completion) {
  548. dispatch_async(dispatch_get_main_queue(), ^{
  549. completion();
  550. });
  551. }
  552. });
  553. }
  554. - (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
  555. dispatch_async(self.ioQueue, ^{
  556. [self.diskCache removeExpiredData];
  557. if (completionBlock) {
  558. dispatch_async(dispatch_get_main_queue(), ^{
  559. completionBlock();
  560. });
  561. }
  562. });
  563. }
  564. #pragma mark - UIApplicationWillTerminateNotification
  565. #if SD_UIKIT || SD_MAC
  566. - (void)applicationWillTerminate:(NSNotification *)notification {
  567. [self deleteOldFilesWithCompletionBlock:nil];
  568. }
  569. #endif
  570. #pragma mark - UIApplicationDidEnterBackgroundNotification
  571. #if SD_UIKIT
  572. - (void)applicationDidEnterBackground:(NSNotification *)notification {
  573. if (!self.config.shouldRemoveExpiredDataWhenEnterBackground) {
  574. return;
  575. }
  576. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  577. if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
  578. return;
  579. }
  580. UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
  581. __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
  582. // Clean up any unfinished task business by marking where you
  583. // stopped or ending the task outright.
  584. [application endBackgroundTask:bgTask];
  585. bgTask = UIBackgroundTaskInvalid;
  586. }];
  587. // Start the long-running task and return immediately.
  588. [self deleteOldFilesWithCompletionBlock:^{
  589. [application endBackgroundTask:bgTask];
  590. bgTask = UIBackgroundTaskInvalid;
  591. }];
  592. }
  593. #endif
  594. #pragma mark - Cache Info
  595. - (NSUInteger)totalDiskSize {
  596. __block NSUInteger size = 0;
  597. dispatch_sync(self.ioQueue, ^{
  598. size = [self.diskCache totalSize];
  599. });
  600. return size;
  601. }
  602. - (NSUInteger)totalDiskCount {
  603. __block NSUInteger count = 0;
  604. dispatch_sync(self.ioQueue, ^{
  605. count = [self.diskCache totalCount];
  606. });
  607. return count;
  608. }
  609. - (void)calculateSizeWithCompletionBlock:(nullable SDImageCacheCalculateSizeBlock)completionBlock {
  610. dispatch_async(self.ioQueue, ^{
  611. NSUInteger fileCount = [self.diskCache totalCount];
  612. NSUInteger totalSize = [self.diskCache totalSize];
  613. if (completionBlock) {
  614. dispatch_async(dispatch_get_main_queue(), ^{
  615. completionBlock(fileCount, totalSize);
  616. });
  617. }
  618. });
  619. }
  620. #pragma mark - Helper
  621. + (SDWebImageOptions)imageOptionsFromCacheOptions:(SDImageCacheOptions)cacheOptions {
  622. SDWebImageOptions options = 0;
  623. if (cacheOptions & SDImageCacheScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages;
  624. if (cacheOptions & SDImageCacheDecodeFirstFrameOnly) options |= SDWebImageDecodeFirstFrameOnly;
  625. if (cacheOptions & SDImageCachePreloadAllFrames) options |= SDWebImagePreloadAllFrames;
  626. if (cacheOptions & SDImageCacheAvoidDecodeImage) options |= SDWebImageAvoidDecodeImage;
  627. if (cacheOptions & SDImageCacheMatchAnimatedImageClass) options |= SDWebImageMatchAnimatedImageClass;
  628. return options;
  629. }
  630. @end
  631. @implementation SDImageCache (SDImageCache)
  632. #pragma mark - SDImageCache
  633. - (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
  634. return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
  635. }
  636. - (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
  637. SDImageCacheOptions cacheOptions = 0;
  638. if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
  639. if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
  640. if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
  641. if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
  642. if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
  643. if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
  644. if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
  645. if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;
  646. return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
  647. }
  648. - (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  649. switch (cacheType) {
  650. case SDImageCacheTypeNone: {
  651. [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock];
  652. }
  653. break;
  654. case SDImageCacheTypeMemory: {
  655. [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock];
  656. }
  657. break;
  658. case SDImageCacheTypeDisk: {
  659. [self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock];
  660. }
  661. break;
  662. case SDImageCacheTypeAll: {
  663. [self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock];
  664. }
  665. break;
  666. default: {
  667. if (completionBlock) {
  668. completionBlock();
  669. }
  670. }
  671. break;
  672. }
  673. }
  674. - (void)removeImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
  675. switch (cacheType) {
  676. case SDImageCacheTypeNone: {
  677. [self removeImageForKey:key fromMemory:NO fromDisk:NO withCompletion:completionBlock];
  678. }
  679. break;
  680. case SDImageCacheTypeMemory: {
  681. [self removeImageForKey:key fromMemory:YES fromDisk:NO withCompletion:completionBlock];
  682. }
  683. break;
  684. case SDImageCacheTypeDisk: {
  685. [self removeImageForKey:key fromMemory:NO fromDisk:YES withCompletion:completionBlock];
  686. }
  687. break;
  688. case SDImageCacheTypeAll: {
  689. [self removeImageForKey:key fromMemory:YES fromDisk:YES withCompletion:completionBlock];
  690. }
  691. break;
  692. default: {
  693. if (completionBlock) {
  694. completionBlock();
  695. }
  696. }
  697. break;
  698. }
  699. }
  700. - (void)containsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheContainsCompletionBlock)completionBlock {
  701. switch (cacheType) {
  702. case SDImageCacheTypeNone: {
  703. if (completionBlock) {
  704. completionBlock(SDImageCacheTypeNone);
  705. }
  706. }
  707. break;
  708. case SDImageCacheTypeMemory: {
  709. BOOL isInMemoryCache = ([self imageFromMemoryCacheForKey:key] != nil);
  710. if (completionBlock) {
  711. completionBlock(isInMemoryCache ? SDImageCacheTypeMemory : SDImageCacheTypeNone);
  712. }
  713. }
  714. break;
  715. case SDImageCacheTypeDisk: {
  716. [self diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
  717. if (completionBlock) {
  718. completionBlock(isInDiskCache ? SDImageCacheTypeDisk : SDImageCacheTypeNone);
  719. }
  720. }];
  721. }
  722. break;
  723. case SDImageCacheTypeAll: {
  724. BOOL isInMemoryCache = ([self imageFromMemoryCacheForKey:key] != nil);
  725. if (isInMemoryCache) {
  726. if (completionBlock) {
  727. completionBlock(SDImageCacheTypeMemory);
  728. }
  729. return;
  730. }
  731. [self diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
  732. if (completionBlock) {
  733. completionBlock(isInDiskCache ? SDImageCacheTypeDisk : SDImageCacheTypeNone);
  734. }
  735. }];
  736. }
  737. break;
  738. default:
  739. if (completionBlock) {
  740. completionBlock(SDImageCacheTypeNone);
  741. }
  742. break;
  743. }
  744. }
  745. - (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
  746. switch (cacheType) {
  747. case SDImageCacheTypeNone: {
  748. if (completionBlock) {
  749. completionBlock();
  750. }
  751. }
  752. break;
  753. case SDImageCacheTypeMemory: {
  754. [self clearMemory];
  755. if (completionBlock) {
  756. completionBlock();
  757. }
  758. }
  759. break;
  760. case SDImageCacheTypeDisk: {
  761. [self clearDiskOnCompletion:completionBlock];
  762. }
  763. break;
  764. case SDImageCacheTypeAll: {
  765. [self clearMemory];
  766. [self clearDiskOnCompletion:completionBlock];
  767. }
  768. break;
  769. default: {
  770. if (completionBlock) {
  771. completionBlock();
  772. }
  773. }
  774. break;
  775. }
  776. }
  777. @end