SDImageCacheTests.m 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  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 "SDTestCase.h"
  9. #import "SDWebImageTestCoder.h"
  10. #import "SDMockFileManager.h"
  11. #import "SDWebImageTestCache.h"
  12. static NSString *kTestImageKeyJPEG = @"TestImageKey.jpg";
  13. static NSString *kTestImageKeyPNG = @"TestImageKey.png";
  14. @interface SDImageCacheTests : SDTestCase <NSFileManagerDelegate>
  15. @end
  16. @implementation SDImageCacheTests
  17. - (void)test01SharedImageCache {
  18. expect([SDImageCache sharedImageCache]).toNot.beNil();
  19. }
  20. - (void)test02Singleton{
  21. expect([SDImageCache sharedImageCache]).to.equal([SDImageCache sharedImageCache]);
  22. }
  23. - (void)test03ImageCacheCanBeInstantiated {
  24. SDImageCache *imageCache = [[SDImageCache alloc] init];
  25. expect(imageCache).toNot.equal([SDImageCache sharedImageCache]);
  26. }
  27. - (void)test04ClearDiskCache{
  28. XCTestExpectation *expectation = [self expectationWithDescription:@"Clear disk cache"];
  29. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  30. [[SDImageCache sharedImageCache] clearDiskOnCompletion:^{
  31. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.equal([self testJPEGImage]);
  32. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  33. if (!isInCache) {
  34. [[SDImageCache sharedImageCache] calculateSizeWithCompletionBlock:^(NSUInteger fileCount, NSUInteger totalSize) {
  35. expect(fileCount).to.equal(0);
  36. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  37. [expectation fulfill];
  38. }];
  39. }];
  40. } else {
  41. XCTFail(@"Image should not be in cache");
  42. }
  43. }];
  44. }];
  45. }];
  46. [self waitForExpectationsWithCommonTimeout];
  47. }
  48. - (void)test05ClearMemoryCache{
  49. XCTestExpectation *expectation = [self expectationWithDescription:@"Clear memory cache"];
  50. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  51. [[SDImageCache sharedImageCache] clearMemory];
  52. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.beNil;
  53. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  54. if (isInCache) {
  55. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  56. [expectation fulfill];
  57. }];
  58. } else {
  59. XCTFail(@"Image should be in cache");
  60. }
  61. }];
  62. }];
  63. [self waitForExpectationsWithCommonTimeout];
  64. }
  65. // Testing storeImage:forKey:
  66. - (void)test06InsertionOfImage {
  67. XCTestExpectation *expectation = [self expectationWithDescription:@"storeImage forKey"];
  68. UIImage *image = [self testJPEGImage];
  69. [[SDImageCache sharedImageCache] removeImageFromDiskForKey:kTestImageKeyJPEG];
  70. [[SDImageCache sharedImageCache] removeImageFromMemoryForKey:kTestImageKeyJPEG];
  71. [[SDImageCache sharedImageCache] storeImage:image forKey:kTestImageKeyJPEG completion:^{
  72. // Disk cache store in async
  73. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  74. if (isInCache) {
  75. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  76. [expectation fulfill];
  77. }];
  78. } else {
  79. XCTFail(@"Image should be in cache");
  80. }
  81. }];
  82. }];
  83. // Memory cache store in sync
  84. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.equal(image);
  85. [self waitForExpectationsWithCommonTimeout];
  86. }
  87. // Testing storeImage:forKey:toDisk:YES
  88. - (void)test07InsertionOfImageForcingDiskStorage {
  89. XCTestExpectation *expectation = [self expectationWithDescription:@"storeImage forKey toDisk=YES"];
  90. UIImage *image = [self testJPEGImage];
  91. [[SDImageCache sharedImageCache] storeImage:image forKey:kTestImageKeyJPEG toDisk:YES completion:^{
  92. // Disk cache store in async
  93. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  94. if (isInCache) {
  95. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  96. [expectation fulfill];
  97. }];
  98. } else {
  99. XCTFail(@"Image should be in cache");
  100. }
  101. }];
  102. }];
  103. // Memory cache store in sync
  104. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.equal(image);
  105. [self waitForExpectationsWithCommonTimeout];
  106. }
  107. // Testing storeImage:forKey:toDisk:NO
  108. - (void)test08InsertionOfImageOnlyInMemory {
  109. XCTestExpectation *expectation = [self expectationWithDescription:@"storeImage forKey toDisk=NO"];
  110. UIImage *image = [self testJPEGImage];
  111. [[SDImageCache sharedImageCache] removeImageFromDiskForKey:kTestImageKeyJPEG];
  112. [[SDImageCache sharedImageCache] removeImageFromMemoryForKey:kTestImageKeyJPEG];
  113. [[SDImageCache sharedImageCache] storeImage:image forKey:kTestImageKeyJPEG toDisk:NO completion:^{
  114. // Disk cache store in async
  115. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  116. if (!isInCache) {
  117. [expectation fulfill];
  118. } else {
  119. XCTFail(@"Image should not be in cache");
  120. }
  121. }];
  122. }];
  123. // Memory cache store in sync
  124. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.equal([self testJPEGImage]);
  125. [[SDImageCache sharedImageCache] storeImageToMemory:image forKey:kTestImageKeyJPEG];
  126. [[SDImageCache sharedImageCache] clearMemory];
  127. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.beNil();
  128. [self waitForExpectationsWithCommonTimeout];
  129. }
  130. - (void)test09RetrieveImageThroughNSOperation {
  131. XCTestExpectation *expectation = [self expectationWithDescription:@"queryCacheOperationForKey"];
  132. UIImage *imageForTesting = [self testJPEGImage];
  133. [[SDImageCache sharedImageCache] storeImage:imageForTesting forKey:kTestImageKeyJPEG completion:^{
  134. id<SDWebImageOperation> operation = [[SDImageCache sharedImageCache] queryCacheOperationForKey:kTestImageKeyJPEG done:^(UIImage *image, NSData *data, SDImageCacheType cacheType) {
  135. expect(image).to.equal(imageForTesting);
  136. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  137. [expectation fulfill];
  138. }];
  139. }];
  140. expect(operation).toNot.beNil;
  141. }];
  142. [self waitForExpectationsWithCommonTimeout];
  143. }
  144. - (void)test10RemoveImageForKeyWithCompletion {
  145. XCTestExpectation *expectation = [self expectationWithDescription:@"removeImageForKey"];
  146. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  147. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  148. expect([[SDImageCache sharedImageCache] imageFromDiskCacheForKey:kTestImageKeyJPEG]).to.beNil;
  149. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.beNil;
  150. [expectation fulfill];
  151. }];
  152. }];
  153. [self waitForExpectationsWithCommonTimeout];
  154. }
  155. - (void)test11RemoveImageforKeyNotFromDiskWithCompletion{
  156. XCTestExpectation *expectation = [self expectationWithDescription:@"removeImageForKey fromDisk:NO"];
  157. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  158. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG fromDisk:NO withCompletion:^{
  159. expect([[SDImageCache sharedImageCache] imageFromDiskCacheForKey:kTestImageKeyJPEG]).toNot.beNil;
  160. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.beNil;
  161. [expectation fulfill];
  162. }];
  163. }];
  164. [self waitForExpectationsWithCommonTimeout];
  165. }
  166. - (void)test12RemoveImageforKeyFromDiskWithCompletion{
  167. XCTestExpectation *expectation = [self expectationWithDescription:@"removeImageForKey fromDisk:YES"];
  168. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  169. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG fromDisk:YES withCompletion:^{
  170. expect([[SDImageCache sharedImageCache] imageFromDiskCacheForKey:kTestImageKeyJPEG]).to.beNil;
  171. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).to.beNil;
  172. [expectation fulfill];
  173. }];
  174. }];
  175. [self waitForExpectationsWithCommonTimeout];
  176. }
  177. - (void)test13DeleteOldFiles {
  178. XCTestExpectation *expectation = [self expectationWithDescription:@"deleteOldFiles"];
  179. [SDImageCache sharedImageCache].config.maxDiskAge = 1; // 1 second to mark all as out-dated
  180. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  181. [[SDImageCache sharedImageCache] deleteOldFilesWithCompletionBlock:^{
  182. expect(SDImageCache.sharedImageCache.totalDiskCount).equal(0);
  183. [expectation fulfill];
  184. }];
  185. });
  186. [self waitForExpectationsWithCommonTimeout];
  187. }
  188. - (void)test14QueryCacheFirstFrameOnlyHitMemoryCache {
  189. NSString *key = kTestGIFURL;
  190. UIImage *animatedImage = [self testGIFImage];
  191. [[SDImageCache sharedImageCache] storeImageToMemory:animatedImage forKey:key];
  192. [[SDImageCache sharedImageCache] queryCacheOperationForKey:key done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  193. expect(cacheType).equal(SDImageCacheTypeMemory);
  194. expect(image.sd_isAnimated).beTruthy();
  195. expect(image == animatedImage).beTruthy();
  196. }];
  197. [[SDImageCache sharedImageCache] queryCacheOperationForKey:key options:SDImageCacheDecodeFirstFrameOnly done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  198. expect(cacheType).equal(SDImageCacheTypeMemory);
  199. expect(image.sd_isAnimated).beFalsy();
  200. expect(image == animatedImage).beFalsy();
  201. }];
  202. [[SDImageCache sharedImageCache] removeImageFromMemoryForKey:kTestGIFURL];
  203. }
  204. - (void)test15CancelQueryShouldCallbackOnceInSync {
  205. XCTestExpectation *expectation = [self expectationWithDescription:@"Cancel Query Should Callback Once In Sync"];
  206. expectation.expectedFulfillmentCount = 1;
  207. NSString *key = @"test15CancelQueryShouldCallbackOnceInSync";
  208. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:key];
  209. [SDImageCache.sharedImageCache removeImageFromDiskForKey:key];
  210. __block BOOL callced = NO;
  211. SDImageCacheToken *token = [SDImageCache.sharedImageCache queryCacheOperationForKey:key done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  212. callced = YES;
  213. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
  214. [expectation fulfill]; // callback once fulfill once
  215. });
  216. }];
  217. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
  218. expect(callced).beFalsy();
  219. [token cancel]; // sync
  220. expect(callced).beTruthy();
  221. });
  222. [self waitForExpectationsWithCommonTimeout];
  223. }
  224. - (void)test20InitialCacheSize{
  225. expect([[SDImageCache sharedImageCache] totalDiskSize]).to.equal(0);
  226. }
  227. - (void)test21InitialDiskCount{
  228. XCTestExpectation *expectation = [self expectationWithDescription:@"getDiskCount"];
  229. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  230. expect([[SDImageCache sharedImageCache] totalDiskCount]).to.equal(1);
  231. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  232. [expectation fulfill];
  233. }];
  234. }];
  235. [self waitForExpectationsWithCommonTimeout];
  236. }
  237. - (void)test31CachePathForAnyKey{
  238. NSString *path = [[SDImageCache sharedImageCache] cachePathForKey:kTestImageKeyJPEG];
  239. expect(path).toNot.beNil;
  240. }
  241. - (void)test32CachePathForNilKey{
  242. NSString *path = [[SDImageCache sharedImageCache] cachePathForKey:nil];
  243. expect(path).to.beNil;
  244. }
  245. - (void)test33CachePathForExistingKey{
  246. XCTestExpectation *expectation = [self expectationWithDescription:@"cachePathForKey inPath"];
  247. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG completion:^{
  248. NSString *path = [[SDImageCache sharedImageCache] cachePathForKey:kTestImageKeyJPEG];
  249. expect(path).notTo.beNil;
  250. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  251. [expectation fulfill];
  252. }];
  253. }];
  254. [self waitForExpectationsWithCommonTimeout];
  255. }
  256. - (void)test34CachePathForSimpleKeyWithExtension {
  257. NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:kTestJPEGURL];
  258. expect(cachePath).toNot.beNil();
  259. expect([cachePath pathExtension]).to.equal(@"jpg");
  260. }
  261. - (void)test35CachePathForKeyWithDotButNoExtension {
  262. NSString *urlString = @"https://maps.googleapis.com/maps/api/staticmap?center=48.8566,2.3522&format=png&maptype=roadmap&scale=2&size=375x200&zoom=15";
  263. NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:urlString];
  264. expect(cachePath).toNot.beNil();
  265. expect([cachePath pathExtension]).to.equal(@"");
  266. }
  267. - (void)test36CachePathForKeyWithURLQueryParams {
  268. NSString *urlString = @"https://imggen.alicdn.com/3b11cea896a9438329d85abfb07b30a8.jpg?aid=tanx&tid=1166&m=%7B%22img_url%22%3A%22https%3A%2F%2Fgma.alicdn.com%2Fbao%2Fuploaded%2Fi4%2F1695306010722305097%2FTB2S2KjkHtlpuFjSspoXXbcDpXa_%21%210-saturn_solar.jpg_sum.jpg%22%2C%22title%22%3A%22%E6%A4%8D%E7%89%A9%E8%94%B7%E8%96%87%E7%8E%AB%E7%91%B0%E8%8A%B1%22%2C%22promot_name%22%3A%22%22%2C%22itemid%22%3A%22546038044448%22%7D&e=cb88dab197bfaa19804f6ec796ca906dab536b88fe6d4475795c7ee661a7ede1&size=640x246";
  269. NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:urlString];
  270. expect(cachePath).toNot.beNil();
  271. expect([cachePath pathExtension]).to.equal(@"jpg");
  272. }
  273. - (void)test37CachePathForKeyWithTooLongExtension {
  274. NSString *urlString = @"https://imggen.alicdn.com/3b11cea896a9438329d85abfb07b30a8.jpgasaaaaaaaaaaaaaaaaaaaajjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjaaaaaaaaaaaaaaaaajjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj";
  275. NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:urlString];
  276. expect(cachePath).toNot.beNil();
  277. expect([cachePath pathExtension]).to.equal(@"");
  278. }
  279. - (void)test40InsertionOfImageData {
  280. XCTestExpectation *expectation = [self expectationWithDescription:@"Insertion of image data works"];
  281. UIImage *image = [[UIImage alloc] initWithContentsOfFile:[self testJPEGPath]];
  282. NSData *imageData = [image sd_imageDataAsFormat:SDImageFormatJPEG];
  283. [[SDImageCache sharedImageCache] storeImageDataToDisk:imageData forKey:kTestImageKeyJPEG];
  284. expect([[SDImageCache sharedImageCache] diskImageDataExistsWithKey:kTestImageKeyJPEG]).beTruthy();
  285. UIImage *storedImageFromMemory = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG];
  286. expect(storedImageFromMemory).to.equal(nil);
  287. NSString *cachePath = [[SDImageCache sharedImageCache] cachePathForKey:kTestImageKeyJPEG];
  288. UIImage *cachedImage = [[UIImage alloc] initWithContentsOfFile:cachePath];
  289. NSData *storedImageData = [cachedImage sd_imageDataAsFormat:SDImageFormatJPEG];
  290. expect(storedImageData.length).to.beGreaterThan(0);
  291. expect(cachedImage.size).to.equal(image.size);
  292. // can't directly compare image and cachedImage because apparently there are some slight differences, even though the image is the same
  293. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  294. expect(isInCache).to.equal(YES);
  295. [[SDImageCache sharedImageCache] removeImageForKey:kTestImageKeyJPEG withCompletion:^{
  296. [expectation fulfill];
  297. }];
  298. }];
  299. [self waitForExpectationsWithCommonTimeout];
  300. }
  301. - (void)test41ThatCustomDecoderWorksForImageCache {
  302. XCTestExpectation *expectation = [self expectationWithDescription:@"Custom decoder for SDImageCache not works"];
  303. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"TestDecode"];
  304. SDWebImageTestCoder *testDecoder = [[SDWebImageTestCoder alloc] init];
  305. [[SDImageCodersManager sharedManager] addCoder:testDecoder];
  306. NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
  307. UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
  308. NSString *key = @"TestPNGImageEncodedToDataAndRetrieveToJPEG";
  309. [cache storeImage:image imageData:nil forKey:key toDisk:YES completion:^{
  310. [cache clearMemory];
  311. #pragma clang diagnostic push
  312. #pragma clang diagnostic ignored "-Wundeclared-selector"
  313. SEL diskImageDataBySearchingAllPathsForKey = @selector(diskImageDataBySearchingAllPathsForKey:);
  314. #pragma clang diagnostic pop
  315. #pragma clang diagnostic push
  316. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  317. NSData *data = [cache performSelector:diskImageDataBySearchingAllPathsForKey withObject:key];
  318. #pragma clang diagnostic pop
  319. NSString *str1 = @"TestEncode";
  320. NSString *str2 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  321. if (![str1 isEqualToString:str2]) {
  322. XCTFail(@"Custom decoder not work for SDImageCache, check -[SDWebImageTestDecoder encodedDataWithImage:format:]");
  323. }
  324. UIImage *diskCacheImage = [cache imageFromDiskCacheForKey:key];
  325. // Decoded result is JPEG
  326. NSString * decodedImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
  327. UIImage *testJPEGImage = [[UIImage alloc] initWithContentsOfFile:decodedImagePath];
  328. NSData *data1 = [testJPEGImage sd_imageDataAsFormat:SDImageFormatPNG];
  329. NSData *data2 = [diskCacheImage sd_imageDataAsFormat:SDImageFormatPNG];
  330. if (![data1 isEqualToData:data2]) {
  331. XCTFail(@"Custom decoder not work for SDImageCache, check -[SDWebImageTestDecoder decodedImageWithData:]");
  332. }
  333. [[SDImageCodersManager sharedManager] removeCoder:testDecoder];
  334. [[SDImageCache sharedImageCache] removeImageForKey:key withCompletion:^{
  335. [expectation fulfill];
  336. }];
  337. }];
  338. [self waitForExpectationsWithCommonTimeout];
  339. }
  340. - (void)test41StoreImageDataToDiskWithCustomFileManager {
  341. NSData *imageData = [NSData dataWithContentsOfFile:[self testJPEGPath]];
  342. NSError *targetError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteNoPermissionError userInfo:nil];
  343. SDMockFileManager *fileManager = [[SDMockFileManager alloc] init];
  344. fileManager.mockSelectors = @{NSStringFromSelector(@selector(createDirectoryAtPath:withIntermediateDirectories:attributes:error:)) : targetError};
  345. expect(fileManager.lastError).to.beNil();
  346. SDImageCacheConfig *config = [SDImageCacheConfig new];
  347. config.fileManager = fileManager;
  348. // This disk cache path creation will be mocked with error.
  349. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"test" diskCacheDirectory:@"/" config:config];
  350. [cache storeImageDataToDisk:imageData
  351. forKey:kTestImageKeyJPEG];
  352. expect(fileManager.lastError).equal(targetError);
  353. }
  354. - (void)test41MatchAnimatedImageClassWorks {
  355. XCTestExpectation *expectation = [self expectationWithDescription:@"MatchAnimatedImageClass option should work"];
  356. UIImage *image = [[UIImage alloc] initWithContentsOfFile:self.testGIFPath];
  357. NSString *kAnimatedImageKey = @"kAnimatedImageKey";
  358. // Store UIImage into cache
  359. [[SDImageCache sharedImageCache] storeImageToMemory:image forKey:kAnimatedImageKey];
  360. // `MatchAnimatedImageClass` will cause query failed because class does not match
  361. [SDImageCache.sharedImageCache queryCacheOperationForKey:kAnimatedImageKey options:SDImageCacheMatchAnimatedImageClass context:@{SDWebImageContextAnimatedImageClass : SDAnimatedImage.class} done:^(UIImage * _Nullable image1, NSData * _Nullable data1, SDImageCacheType cacheType1) {
  362. expect(image1).beNil();
  363. // This should query success with UIImage
  364. [SDImageCache.sharedImageCache queryCacheOperationForKey:kAnimatedImageKey options:0 context:@{SDWebImageContextAnimatedImageClass : SDAnimatedImage.class} done:^(UIImage * _Nullable image2, NSData * _Nullable data2, SDImageCacheType cacheType2) {
  365. expect(image2).notTo.beNil();
  366. expect(image2).equal(image);
  367. [expectation fulfill];
  368. }];
  369. }];
  370. // Test sync version API `imageFromCacheForKey` as well
  371. expect([SDImageCache.sharedImageCache imageFromCacheForKey:kAnimatedImageKey options:SDImageCacheMatchAnimatedImageClass context:@{SDWebImageContextAnimatedImageClass : SDAnimatedImage.class}]).beNil();
  372. expect([SDImageCache.sharedImageCache imageFromCacheForKey:kAnimatedImageKey options:0 context:@{SDWebImageContextAnimatedImageClass : SDAnimatedImage.class}]).notTo.beNil();
  373. [self waitForExpectationsWithCommonTimeout];
  374. }
  375. - (void)test42StoreCacheWithImageAndFormatWithoutImageData {
  376. XCTestExpectation *expectation1 = [self expectationWithDescription:@"StoreImage UIImage without sd_imageFormat should use PNG for alpha channel"];
  377. XCTestExpectation *expectation2 = [self expectationWithDescription:@"StoreImage UIImage without sd_imageFormat should use JPEG for non-alpha channel"];
  378. XCTestExpectation *expectation3 = [self expectationWithDescription:@"StoreImage UIImage/UIAnimatedImage with sd_imageFormat should use that format"];
  379. XCTestExpectation *expectation4 = [self expectationWithDescription:@"StoreImage SDAnimatedImage should use animatedImageData"];
  380. XCTestExpectation *expectation5 = [self expectationWithDescription:@"StoreImage UIAnimatedImage without sd_imageFormat should use GIF"];
  381. NSString *kAnimatedImageKey1 = @"kAnimatedImageKey1";
  382. NSString *kAnimatedImageKey2 = @"kAnimatedImageKey2";
  383. NSString *kAnimatedImageKey3 = @"kAnimatedImageKey3";
  384. NSString *kAnimatedImageKey4 = @"kAnimatedImageKey4";
  385. NSString *kAnimatedImageKey5 = @"kAnimatedImageKey5";
  386. // Case 1: UIImage without `sd_imageFormat` should use PNG for alpha channel
  387. NSData *pngData = [NSData dataWithContentsOfFile:[self testPNGPath]];
  388. UIImage *pngImage = [UIImage sd_imageWithData:pngData];
  389. expect(pngImage.sd_isAnimated).beFalsy();
  390. expect(pngImage.sd_imageFormat).equal(SDImageFormatPNG);
  391. // Remove sd_imageFormat
  392. pngImage.sd_imageFormat = SDImageFormatUndefined;
  393. // Check alpha channel
  394. expect([SDImageCoderHelper CGImageContainsAlpha:pngImage.CGImage]).beTruthy();
  395. [SDImageCache.sharedImageCache storeImage:pngImage forKey:kAnimatedImageKey1 toDisk:YES completion:^{
  396. UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey1];
  397. // Should save to PNG
  398. expect(diskImage.sd_isAnimated).beFalsy();
  399. expect(diskImage.sd_imageFormat).equal(SDImageFormatPNG);
  400. [expectation1 fulfill];
  401. }];
  402. // Case 2: UIImage without `sd_imageFormat` should use JPEG for non-alpha channel
  403. SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat];
  404. format.opaque = YES;
  405. SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:pngImage.size format:format];
  406. // Non-alpha image, also test `SDGraphicsImageRenderer` behavior here :)
  407. UIImage *nonAlphaImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
  408. [pngImage drawInRect:CGRectMake(0, 0, pngImage.size.width, pngImage.size.height)];
  409. }];
  410. expect(nonAlphaImage).notTo.beNil();
  411. expect([SDImageCoderHelper CGImageContainsAlpha:nonAlphaImage.CGImage]).beFalsy();
  412. [SDImageCache.sharedImageCache storeImage:nonAlphaImage forKey:kAnimatedImageKey2 toDisk:YES completion:^{
  413. UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey2];
  414. // Should save to JPEG
  415. expect(diskImage.sd_isAnimated).beFalsy();
  416. expect(diskImage.sd_imageFormat).equal(SDImageFormatJPEG);
  417. [expectation2 fulfill];
  418. }];
  419. NSData *gifData = [NSData dataWithContentsOfFile:[self testGIFPath]];
  420. UIImage *gifImage = [UIImage sd_imageWithData:gifData]; // UIAnimatedImage
  421. expect(gifImage.sd_isAnimated).beTruthy();
  422. expect(gifImage.sd_imageFormat).equal(SDImageFormatGIF);
  423. // Case 3: UIImage with `sd_imageFormat` should use that format
  424. [SDImageCache.sharedImageCache storeImage:gifImage forKey:kAnimatedImageKey3 toDisk:YES completion:^{
  425. UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey3];
  426. // Should save to GIF
  427. expect(diskImage.sd_isAnimated).beTruthy();
  428. expect(diskImage.sd_imageFormat).equal(SDImageFormatGIF);
  429. [expectation3 fulfill];
  430. }];
  431. // Case 4: SDAnimatedImage should use `animatedImageData`
  432. SDAnimatedImage *animatedImage = [SDAnimatedImage imageWithData:gifData];
  433. expect(animatedImage.animatedImageData).notTo.beNil();
  434. [SDImageCache.sharedImageCache storeImage:animatedImage forKey:kAnimatedImageKey4 toDisk:YES completion:^{
  435. NSData *data = [SDImageCache.sharedImageCache diskImageDataForKey:kAnimatedImageKey4];
  436. // Should save with animatedImageData
  437. expect(data).equal(animatedImage.animatedImageData);
  438. [expectation4 fulfill];
  439. }];
  440. // Case 5: UIAnimatedImage without sd_imageFormat should use GIF not APNG
  441. NSData *apngData = [NSData dataWithContentsOfFile:[self testAPNGPath]];
  442. UIImage *apngImage = [UIImage sd_imageWithData:apngData];
  443. expect(apngImage.sd_isAnimated).beTruthy();
  444. expect(apngImage.sd_imageFormat).equal(SDImageFormatPNG);
  445. // Remove sd_imageFormat
  446. apngImage.sd_imageFormat = SDImageFormatUndefined;
  447. [SDImageCache.sharedImageCache storeImage:apngImage forKey:kAnimatedImageKey5 toDisk:YES completion:^{
  448. UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey5];
  449. // Should save to GIF
  450. expect(diskImage.sd_isAnimated).beTruthy();
  451. expect(diskImage.sd_imageFormat).equal(SDImageFormatGIF);
  452. [expectation5 fulfill];
  453. }];
  454. [self waitForExpectationsWithCommonTimeout];
  455. }
  456. - (void)test43CustomDefaultCacheDirectory {
  457. NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  458. NSString *testDirectory = [paths.firstObject stringByAppendingPathComponent:@"CustomDefaultCacheDirectory"];
  459. NSString *defaultDirectory = [paths.firstObject stringByAppendingPathComponent:@"com.hackemist.SDImageCache"];
  460. NSString *namespace = @"Test";
  461. // Default cache path
  462. expect(SDImageCache.defaultDiskCacheDirectory).equal(defaultDirectory);
  463. SDImageCache *cache1 = [[SDImageCache alloc] initWithNamespace:namespace];
  464. expect(cache1.diskCachePath).equal([defaultDirectory stringByAppendingPathComponent:namespace]);
  465. // Custom cache path
  466. SDImageCache.defaultDiskCacheDirectory = testDirectory;
  467. SDImageCache *cache2 = [[SDImageCache alloc] initWithNamespace:namespace];
  468. expect(cache2.diskCachePath).equal([testDirectory stringByAppendingPathComponent:namespace]);
  469. // Check reset
  470. SDImageCache.defaultDiskCacheDirectory = nil;
  471. expect(SDImageCache.defaultDiskCacheDirectory).equal(defaultDirectory);
  472. }
  473. #pragma mark - SDMemoryCache & SDDiskCache
  474. - (void)test42CustomMemoryCache {
  475. SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
  476. config.memoryCacheClass = [SDWebImageTestMemoryCache class];
  477. NSString *nameSpace = @"SDWebImageTestMemoryCache";
  478. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:nameSpace diskCacheDirectory:nil config:config];
  479. SDWebImageTestMemoryCache *memCache = cache.memoryCache;
  480. expect([memCache isKindOfClass:[SDWebImageTestMemoryCache class]]).to.beTruthy();
  481. }
  482. - (void)test43CustomDiskCache {
  483. SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
  484. config.diskCacheClass = [SDWebImageTestDiskCache class];
  485. NSString *nameSpace = @"SDWebImageTestDiskCache";
  486. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:nameSpace diskCacheDirectory:nil config:config];
  487. SDWebImageTestDiskCache *diskCache = cache.diskCache;
  488. expect([diskCache isKindOfClass:[SDWebImageTestDiskCache class]]).to.beTruthy();
  489. }
  490. - (void)test44DiskCacheMigrationFromOldVersion {
  491. SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
  492. NSFileManager *fileManager = [[NSFileManager alloc] init];
  493. config.fileManager = fileManager;
  494. // Fake to store a.png into old path
  495. NSString *newDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"com.hackemist.SDImageCache"] stringByAppendingPathComponent:@"default"];
  496. NSString *oldDefaultPath = [[[self userCacheDirectory] stringByAppendingPathComponent:@"default"] stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default"];
  497. [fileManager createDirectoryAtPath:oldDefaultPath withIntermediateDirectories:YES attributes:nil error:nil];
  498. [fileManager createFileAtPath:[oldDefaultPath stringByAppendingPathComponent:@"a.png"] contents:[NSData dataWithContentsOfFile:[self testPNGPath]] attributes:nil];
  499. // Call migration
  500. SDDiskCache *diskCache = [[SDDiskCache alloc] initWithCachePath:newDefaultPath config:config];
  501. [diskCache moveCacheDirectoryFromPath:oldDefaultPath toPath:newDefaultPath];
  502. // Expect a.png into new path
  503. BOOL exist = [fileManager fileExistsAtPath:[newDefaultPath stringByAppendingPathComponent:@"a.png"]];
  504. expect(exist).beTruthy();
  505. }
  506. - (void)test45DiskCacheRemoveExpiredData {
  507. NSString *cachePath = [[self userCacheDirectory] stringByAppendingPathComponent:@"disk"];
  508. SDImageCacheConfig *config = SDImageCacheConfig.defaultCacheConfig;
  509. config.maxDiskAge = 1; // 1 second
  510. config.maxDiskSize = 10; // 10 KB
  511. SDDiskCache *diskCache = [[SDDiskCache alloc] initWithCachePath:cachePath config:config];
  512. [diskCache removeAllData];
  513. expect(diskCache.totalSize).equal(0);
  514. expect(diskCache.totalCount).equal(0);
  515. // 20KB -> maxDiskSize
  516. NSUInteger length = 20;
  517. void *bytes = malloc(length);
  518. NSData *data = [NSData dataWithBytes:bytes length:length];
  519. free(bytes);
  520. [diskCache setData:data forKey:@"20KB"];
  521. expect(diskCache.totalSize).equal(length);
  522. expect(diskCache.totalCount).equal(1);
  523. [diskCache removeExpiredData];
  524. expect(diskCache.totalSize).equal(0);
  525. expect(diskCache.totalCount).equal(0);
  526. // 1KB with 5s -> maxDiskAge
  527. XCTestExpectation *expectation = [self expectationWithDescription:@"SDDiskCache removeExpireData timeout"];
  528. length = 1;
  529. bytes = malloc(length);
  530. data = [NSData dataWithBytes:bytes length:length];
  531. free(bytes);
  532. [diskCache setData:data forKey:@"1KB"];
  533. expect(diskCache.totalSize).equal(length);
  534. expect(diskCache.totalCount).equal(1);
  535. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  536. [diskCache removeExpiredData];
  537. expect(diskCache.totalSize).equal(0);
  538. expect(diskCache.totalCount).equal(0);
  539. [expectation fulfill];
  540. });
  541. [self waitForExpectationsWithTimeout:5 handler:nil];
  542. }
  543. #if SD_UIKIT
  544. - (void)test46MemoryCacheWeakCache {
  545. SDMemoryCache *memoryCache = [[SDMemoryCache alloc] init];
  546. memoryCache.config.shouldUseWeakMemoryCache = NO;
  547. memoryCache.config.maxMemoryCost = 10;
  548. memoryCache.config.maxMemoryCount = 5;
  549. expect(memoryCache.countLimit).equal(5);
  550. expect(memoryCache.totalCostLimit).equal(10);
  551. // Don't use weak cache
  552. NSObject *object = [NSObject new];
  553. [memoryCache setObject:object forKey:@"1"];
  554. [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  555. NSObject *cachedObject = [memoryCache objectForKey:@"1"];
  556. expect(cachedObject).beNil();
  557. // Use weak cache
  558. memoryCache.config.shouldUseWeakMemoryCache = YES;
  559. object = [NSObject new];
  560. [memoryCache setObject:object forKey:@"1"];
  561. [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  562. cachedObject = [memoryCache objectForKey:@"1"];
  563. expect(object).equal(cachedObject);
  564. }
  565. #endif
  566. - (void)test47DiskCacheExtendedData {
  567. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache extended data read/write works"];
  568. UIImage *image = [self testPNGImage];
  569. NSDictionary *extendedObject = @{@"Test" : @"Object"};
  570. image.sd_extendedObject = extendedObject;
  571. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestImageKeyPNG];
  572. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestImageKeyPNG];
  573. // Write extended data
  574. [SDImageCache.sharedImageCache storeImage:image forKey:kTestImageKeyPNG completion:^{
  575. NSData *extendedData = [SDImageCache.sharedImageCache.diskCache extendedDataForKey:kTestImageKeyPNG];
  576. expect(extendedData).toNot.beNil();
  577. // Read extended data
  578. UIImage *newImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kTestImageKeyPNG];
  579. id newExtendedObject = newImage.sd_extendedObject;
  580. expect(extendedObject).equal(newExtendedObject);
  581. // Remove extended data
  582. [SDImageCache.sharedImageCache.diskCache setExtendedData:nil forKey:kTestImageKeyPNG];
  583. [expectation fulfill];
  584. }];
  585. [self waitForExpectationsWithCommonTimeout];
  586. }
  587. - (void)test48CacheUseConcurrentIOQueue {
  588. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache concurrent ioQueue"];
  589. expectation.expectedFulfillmentCount = 2;
  590. SDImageCacheConfig *config = [SDImageCacheConfig new];
  591. dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_BACKGROUND, 0);
  592. config.ioQueueAttributes = attr;
  593. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"Concurrent" diskCacheDirectory:@"/" config:config];
  594. NSData *pngData = [NSData dataWithContentsOfFile:[self testPNGPath]];
  595. // Added test case for custom queue
  596. [SDCallbackQueue.globalQueue async:^{
  597. SDWebImageContext *context = @{SDWebImageContextCallbackQueue : SDCallbackQueue.currentQueue};
  598. expect(NSThread.isMainThread).beFalsy();
  599. [cache queryCacheOperationForKey:@"Key1" options:0 context:context done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  600. expect(data).beNil();
  601. expect(NSThread.isMainThread).beFalsy();
  602. [expectation fulfill];
  603. }];
  604. [cache storeImageData:pngData forKey:@"Key1" completion:^{
  605. [expectation fulfill];
  606. }];
  607. }];
  608. [self waitForExpectationsWithCommonTimeout];
  609. }
  610. #pragma mark - SDImageCache & SDImageCachesManager
  611. - (void)test49SDImageCacheQueryOp {
  612. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"];
  613. NSData *imageData = [[SDImageCodersManager sharedManager] encodedDataWithImage:[self testJPEGImage] format:SDImageFormatJPEG options:nil];
  614. [[SDImageCache sharedImageCache] storeImageDataToDisk:imageData forKey:kTestImageKeyJPEG];
  615. [[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:@{SDWebImageContextStoreCacheType : @(SDImageCacheTypeDisk)} cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  616. expect(image).notTo.beNil();
  617. expect([[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG]).beNil();
  618. [expectation fulfill];
  619. }];
  620. [self waitForExpectationsWithCommonTimeout];
  621. }
  622. - (void)test50SDImageCacheQueryOp {
  623. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"];
  624. [[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG toDisk:NO completion:nil];
  625. [[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  626. expect(image).notTo.beNil();
  627. [expectation fulfill];
  628. }];
  629. [self waitForExpectationsWithCommonTimeout];
  630. }
  631. - (void)test51SDImageCacheStoreOp {
  632. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache store op works"];
  633. [[SDImageCachesManager sharedManager] storeImage:[self testJPEGImage] imageData:nil forKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeAll completion:^{
  634. UIImage *image = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG];
  635. expect(image).notTo.beNil();
  636. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  637. expect(isInCache).to.beTruthy();
  638. [expectation fulfill];
  639. }];
  640. }];
  641. [self waitForExpectationsWithCommonTimeout];
  642. }
  643. - (void)test52SDImageCacheRemoveOp {
  644. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache remove op works"];
  645. [[SDImageCachesManager sharedManager] removeImageForKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeDisk completion:^{
  646. UIImage *memoryImage = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG];
  647. expect(memoryImage).notTo.beNil();
  648. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  649. expect(isInCache).to.beFalsy();
  650. [expectation fulfill];
  651. }];
  652. }];
  653. [self waitForExpectationsWithCommonTimeout];
  654. }
  655. - (void)test53SDImageCacheContainsOp {
  656. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache contains op works"];
  657. [[SDImageCachesManager sharedManager] containsImageForKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
  658. expect(containsCacheType).equal(SDImageCacheTypeMemory);
  659. [expectation fulfill];
  660. }];
  661. [self waitForExpectationsWithCommonTimeout];
  662. }
  663. - (void)test54SDImageCacheClearOp {
  664. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache clear op works"];
  665. [[SDImageCachesManager sharedManager] clearWithCacheType:SDImageCacheTypeAll completion:^{
  666. UIImage *memoryImage = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:kTestImageKeyJPEG];
  667. expect(memoryImage).to.beNil();
  668. [[SDImageCache sharedImageCache] diskImageExistsWithKey:kTestImageKeyJPEG completion:^(BOOL isInCache) {
  669. expect(isInCache).to.beFalsy();
  670. [expectation fulfill];
  671. }];
  672. }];
  673. [self waitForExpectationsWithCommonTimeout];
  674. }
  675. - (void)test55SDImageCachesManagerOperationPolicySimple {
  676. SDImageCachesManager *cachesManager = [[SDImageCachesManager alloc] init];
  677. SDImageCache *cache1 = [[SDImageCache alloc] initWithNamespace:@"cache1"];
  678. SDImageCache *cache2 = [[SDImageCache alloc] initWithNamespace:@"cache2"];
  679. cachesManager.caches = @[cache1, cache2];
  680. [[NSFileManager defaultManager] removeItemAtPath:cache1.diskCachePath error:nil];
  681. [[NSFileManager defaultManager] removeItemAtPath:cache2.diskCachePath error:nil];
  682. // LowestOnly
  683. cachesManager.queryOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
  684. cachesManager.storeOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
  685. cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
  686. cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
  687. cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
  688. [cachesManager queryImageForKey:kTestImageKeyJPEG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  689. expect(image).to.beNil();
  690. }];
  691. [cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeMemory completion:nil];
  692. // Check Logic works, cache1 only
  693. UIImage *memoryImage1 = [cache1 imageFromMemoryCacheForKey:kTestImageKeyJPEG];
  694. expect(memoryImage1).equal([self testJPEGImage]);
  695. [cachesManager containsImageForKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeMemory completion:^(SDImageCacheType containsCacheType) {
  696. expect(containsCacheType).equal(SDImageCacheTypeMemory);
  697. }];
  698. [cachesManager removeImageForKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeMemory completion:nil];
  699. [cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil];
  700. // HighestOnly
  701. cachesManager.queryOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
  702. cachesManager.storeOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
  703. cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
  704. cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
  705. cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
  706. [cachesManager queryImageForKey:kTestImageKeyPNG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  707. expect(image).to.beNil();
  708. }];
  709. [cachesManager storeImage:[self testPNGImage] imageData:nil forKey:kTestImageKeyPNG cacheType:SDImageCacheTypeMemory completion:nil];
  710. // Check Logic works, cache2 only
  711. UIImage *memoryImage2 = [cache2 imageFromMemoryCacheForKey:kTestImageKeyPNG];
  712. expect(memoryImage2).equal([self testPNGImage]);
  713. [cachesManager containsImageForKey:kTestImageKeyPNG cacheType:SDImageCacheTypeMemory completion:^(SDImageCacheType containsCacheType) {
  714. expect(containsCacheType).equal(SDImageCacheTypeMemory);
  715. }];
  716. [cachesManager removeImageForKey:kTestImageKeyPNG cacheType:SDImageCacheTypeMemory completion:nil];
  717. [cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil];
  718. }
  719. - (void)test56SDImageCachesManagerOperationPolicyConcurrent {
  720. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCachesManager operation cocurrent policy works"];
  721. SDImageCachesManager *cachesManager = [[SDImageCachesManager alloc] init];
  722. SDImageCache *cache1 = [[SDImageCache alloc] initWithNamespace:@"cache1"];
  723. SDImageCache *cache2 = [[SDImageCache alloc] initWithNamespace:@"cache2"];
  724. cachesManager.caches = @[cache1, cache2];
  725. [[NSFileManager defaultManager] removeItemAtPath:cache1.diskCachePath error:nil];
  726. [[NSFileManager defaultManager] removeItemAtPath:cache2.diskCachePath error:nil];
  727. NSString *kConcurrentTestImageKey = @"kConcurrentTestImageKey";
  728. // Cocurrent
  729. // Check all concurrent op
  730. cachesManager.queryOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
  731. cachesManager.storeOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
  732. cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
  733. cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
  734. cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
  735. [cachesManager queryImageForKey:kConcurrentTestImageKey options:0 context:nil cacheType:SDImageCacheTypeAll completion:nil];
  736. [cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
  737. [cachesManager removeImageForKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
  738. [cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil];
  739. // Check Logic works, check cache1(memory+JPEG) & cache2(disk+PNG) at the same time. Cache1(memory) is fast and hit.
  740. [cache1 storeImage:[self testJPEGImage] forKey:kConcurrentTestImageKey toDisk:NO completion:nil];
  741. [cache2 storeImage:[self testPNGImage] forKey:kConcurrentTestImageKey toDisk:YES completion:^{
  742. UIImage *memoryImage1 = [cache1 imageFromMemoryCacheForKey:kConcurrentTestImageKey];
  743. expect(memoryImage1).notTo.beNil();
  744. [cache2 removeImageFromMemoryForKey:kConcurrentTestImageKey];
  745. [cachesManager containsImageForKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
  746. // Cache1 hit
  747. expect(containsCacheType).equal(SDImageCacheTypeMemory);
  748. [expectation fulfill];
  749. }];
  750. }];
  751. [self waitForExpectationsWithCommonTimeout];
  752. }
  753. - (void)test57SDImageCachesManagerOperationPolicySerial {
  754. XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCachesManager operation serial policy works"];
  755. SDImageCachesManager *cachesManager = [[SDImageCachesManager alloc] init];
  756. SDImageCache *cache1 = [[SDImageCache alloc] initWithNamespace:@"cache1"];
  757. SDImageCache *cache2 = [[SDImageCache alloc] initWithNamespace:@"cache2"];
  758. cachesManager.caches = @[cache1, cache2];
  759. NSString *kSerialTestImageKey = @"kSerialTestImageKey";
  760. // Serial
  761. // Check all serial op
  762. cachesManager.queryOperationPolicy = SDImageCachesManagerOperationPolicySerial;
  763. cachesManager.storeOperationPolicy = SDImageCachesManagerOperationPolicySerial;
  764. cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicySerial;
  765. cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicySerial;
  766. cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicySerial;
  767. [cachesManager queryImageForKey:kSerialTestImageKey options:0 context:nil cacheType:SDImageCacheTypeAll completion:nil];
  768. [cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kSerialTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
  769. [cachesManager removeImageForKey:kSerialTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
  770. [cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil];
  771. // Check Logic work, from cache2(disk+PNG) -> cache1(memory+JPEG). Cache2(disk) is slow but hit.
  772. [cache1 storeImage:[self testJPEGImage] forKey:kSerialTestImageKey toDisk:NO completion:nil];
  773. [cache2 storeImage:[self testPNGImage] forKey:kSerialTestImageKey toDisk:YES completion:^{
  774. UIImage *memoryImage1 = [cache1 imageFromMemoryCacheForKey:kSerialTestImageKey];
  775. expect(memoryImage1).notTo.beNil();
  776. [cache2 removeImageFromMemoryForKey:kSerialTestImageKey];
  777. [cachesManager containsImageForKey:kSerialTestImageKey cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
  778. // Cache2 hit
  779. expect(containsCacheType).equal(SDImageCacheTypeDisk);
  780. [expectation fulfill];
  781. }];
  782. }];
  783. [self waitForExpectationsWithCommonTimeout];
  784. }
  785. - (void)test58CustomImageCache {
  786. NSString *cachePath = [[self userCacheDirectory] stringByAppendingPathComponent:@"custom"];
  787. SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
  788. SDWebImageTestCache *cache = [[SDWebImageTestCache alloc] initWithCachePath:cachePath config:config];
  789. expect(cache.memoryCache).notTo.beNil();
  790. expect(cache.diskCache).notTo.beNil();
  791. // Clear
  792. [cache clearWithCacheType:SDImageCacheTypeAll completion:nil];
  793. // Store
  794. UIImage *image1 = self.testJPEGImage;
  795. NSString *key1 = @"testJPEGImage";
  796. [cache storeImage:image1 imageData:nil forKey:key1 cacheType:SDImageCacheTypeAll completion:nil];
  797. // Contain
  798. [cache containsImageForKey:key1 cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
  799. expect(containsCacheType).equal(SDImageCacheTypeMemory);
  800. }];
  801. // Query
  802. [cache queryImageForKey:key1 options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
  803. expect(image).equal(image1);
  804. expect(data).beNil();
  805. expect(cacheType).equal(SDImageCacheTypeMemory);
  806. }];
  807. // Remove
  808. [cache removeImageForKey:key1 cacheType:SDImageCacheTypeAll completion:nil];
  809. // Contain
  810. [cache containsImageForKey:key1 cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
  811. expect(containsCacheType).equal(SDImageCacheTypeNone);
  812. }];
  813. // Clear
  814. [cache clearWithCacheType:SDImageCacheTypeAll completion:nil];
  815. NSArray<NSString *> *cacheFiles = [cache.diskCache.fileManager contentsOfDirectoryAtPath:cachePath error:nil];
  816. expect(cacheFiles.count).equal(0);
  817. }
  818. #pragma mark Helper methods
  819. - (UIImage *)testJPEGImage {
  820. static UIImage *reusableImage = nil;
  821. if (!reusableImage) {
  822. reusableImage = [[UIImage alloc] initWithContentsOfFile:[self testJPEGPath]];
  823. }
  824. return reusableImage;
  825. }
  826. - (UIImage *)testPNGImage {
  827. static UIImage *reusableImage = nil;
  828. if (!reusableImage) {
  829. reusableImage = [[UIImage alloc] initWithContentsOfFile:[self testPNGPath]];
  830. }
  831. return reusableImage;
  832. }
  833. - (UIImage *)testGIFImage {
  834. static UIImage *reusableImage = nil;
  835. if (!reusableImage) {
  836. NSData *data = [NSData dataWithContentsOfFile:[self testGIFPath]];
  837. reusableImage = [UIImage sd_imageWithData:data];
  838. }
  839. return reusableImage;
  840. }
  841. - (UIImage *)testAPNGImage {
  842. static UIImage *reusableImage = nil;
  843. if (!reusableImage) {
  844. NSData *data = [NSData dataWithContentsOfFile:[self testAPNGPath]];
  845. reusableImage = [UIImage sd_imageWithData:data];
  846. }
  847. return reusableImage;
  848. }
  849. - (NSString *)testJPEGPath {
  850. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  851. return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
  852. }
  853. - (NSString *)testPNGPath {
  854. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  855. return [testBundle pathForResource:@"TestImage" ofType:@"png"];
  856. }
  857. - (NSString *)testGIFPath {
  858. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  859. NSString *testPath = [testBundle pathForResource:@"TestImage" ofType:@"gif"];
  860. return testPath;
  861. }
  862. - (NSString *)testAPNGPath {
  863. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  864. NSString *testPath = [testBundle pathForResource:@"TestImageAnimated" ofType:@"apng"];
  865. return testPath;
  866. }
  867. - (nullable NSString *)userCacheDirectory {
  868. NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  869. return paths.firstObject;
  870. }
  871. @end