PageRenderTime 27ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/TeamTalk/Pods/SDWebImage/SDWebImage/SDImageCache.m

https://gitlab.com/lisit1003/TTiOSClient
Objective C | 534 lines | 393 code | 100 blank | 41 comment | 51 complexity | d47f3596043d1f9a8eb66151057547d3 MD5 | raw file
  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 "SDWebImageDecoder.h"
  10. #import "UIImage+MultiFormat.h"
  11. #import <CommonCrypto/CommonDigest.h>
  12. static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
  13. // PNG signature bytes and data (below)
  14. static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
  15. static NSData *kPNGSignatureData = nil;
  16. BOOL ImageDataHasPNGPreffix(NSData *data);
  17. BOOL ImageDataHasPNGPreffix(NSData *data) {
  18. NSUInteger pngSignatureLength = [kPNGSignatureData length];
  19. if ([data length] >= pngSignatureLength) {
  20. if ([[data subdataWithRange:NSMakeRange(0, pngSignatureLength)] isEqualToData:kPNGSignatureData]) {
  21. return YES;
  22. }
  23. }
  24. return NO;
  25. }
  26. @interface SDImageCache ()
  27. @property (strong, nonatomic) NSCache *memCache;
  28. @property (strong, nonatomic) NSString *diskCachePath;
  29. @property (strong, nonatomic) NSMutableArray *customPaths;
  30. @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
  31. @end
  32. @implementation SDImageCache {
  33. NSFileManager *_fileManager;
  34. }
  35. + (SDImageCache *)sharedImageCache {
  36. static dispatch_once_t once;
  37. static id instance;
  38. dispatch_once(&once, ^{
  39. instance = [self new];
  40. kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];
  41. });
  42. return instance;
  43. }
  44. - (id)init {
  45. return [self initWithNamespace:@"default"];
  46. }
  47. - (id)initWithNamespace:(NSString *)ns {
  48. if ((self = [super init])) {
  49. NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
  50. // Create IO serial queue
  51. _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
  52. // Init default values
  53. _maxCacheAge = kDefaultCacheMaxCacheAge;
  54. // Init the memory cache
  55. _memCache = [[NSCache alloc] init];
  56. _memCache.name = fullNamespace;
  57. // Init the disk cache
  58. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  59. _diskCachePath = [paths[0] stringByAppendingPathComponent:fullNamespace];
  60. dispatch_sync(_ioQueue, ^{
  61. _fileManager = [NSFileManager new];
  62. });
  63. #if TARGET_OS_IPHONE
  64. // Subscribe to app events
  65. [[NSNotificationCenter defaultCenter] addObserver:self
  66. selector:@selector(clearMemory)
  67. name:UIApplicationDidReceiveMemoryWarningNotification
  68. object:nil];
  69. [[NSNotificationCenter defaultCenter] addObserver:self
  70. selector:@selector(cleanDisk)
  71. name:UIApplicationWillTerminateNotification
  72. object:nil];
  73. [[NSNotificationCenter defaultCenter] addObserver:self
  74. selector:@selector(backgroundCleanDisk)
  75. name:UIApplicationDidEnterBackgroundNotification
  76. object:nil];
  77. #endif
  78. }
  79. return self;
  80. }
  81. - (void)dealloc {
  82. [[NSNotificationCenter defaultCenter] removeObserver:self];
  83. SDDispatchQueueRelease(_ioQueue);
  84. }
  85. - (void)addReadOnlyCachePath:(NSString *)path {
  86. if (!self.customPaths) {
  87. self.customPaths = [NSMutableArray new];
  88. }
  89. if (![self.customPaths containsObject:path]) {
  90. [self.customPaths addObject:path];
  91. }
  92. }
  93. - (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
  94. NSString *filename = [self cachedFileNameForKey:key];
  95. return [path stringByAppendingPathComponent:filename];
  96. }
  97. - (NSString *)defaultCachePathForKey:(NSString *)key {
  98. return [self cachePathForKey:key inPath:self.diskCachePath];
  99. }
  100. #pragma mark SDImageCache (private)
  101. - (NSString *)cachedFileNameForKey:(NSString *)key {
  102. const char *str = [key UTF8String];
  103. if (str == NULL) {
  104. str = "";
  105. }
  106. unsigned char r[CC_MD5_DIGEST_LENGTH];
  107. CC_MD5(str, (CC_LONG)strlen(str), r);
  108. NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
  109. 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]];
  110. return filename;
  111. }
  112. #pragma mark ImageCache
  113. - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
  114. if (!image || !key) {
  115. return;
  116. }
  117. [self.memCache setObject:image forKey:key cost:image.size.height * image.size.width * image.scale];
  118. if (toDisk) {
  119. dispatch_async(self.ioQueue, ^{
  120. NSData *data = imageData;
  121. if (image && (recalculate || !data)) {
  122. #if TARGET_OS_IPHONE
  123. // We need to determine if the image is a PNG or a JPEG
  124. // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
  125. // The first eight bytes of a PNG file always contain the following (decimal) values:
  126. // 137 80 78 71 13 10 26 10
  127. // We assume the image is PNG, in case the imageData is nil (i.e. if trying to save a UIImage directly),
  128. // we will consider it PNG to avoid loosing the transparency
  129. BOOL imageIsPng = YES;
  130. // But if we have an image data, we will look at the preffix
  131. if ([imageData length] >= [kPNGSignatureData length]) {
  132. imageIsPng = ImageDataHasPNGPreffix(imageData);
  133. }
  134. if (imageIsPng) {
  135. data = UIImagePNGRepresentation(image);
  136. }
  137. else {
  138. data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
  139. }
  140. #else
  141. data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
  142. #endif
  143. }
  144. if (data) {
  145. if (![_fileManager fileExistsAtPath:_diskCachePath]) {
  146. [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
  147. }
  148. [_fileManager createFileAtPath:[self defaultCachePathForKey:key] contents:data attributes:nil];
  149. }
  150. });
  151. }
  152. }
  153. - (void)storeImage:(UIImage *)image forKey:(NSString *)key {
  154. [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:YES];
  155. }
  156. - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk {
  157. [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:toDisk];
  158. }
  159. - (BOOL)diskImageExistsWithKey:(NSString *)key {
  160. BOOL exists = NO;
  161. // this is an exception to access the filemanager on another queue than ioQueue, but we are using the shared instance
  162. // from apple docs on NSFileManager: The methods of the shared NSFileManager object can be called from multiple threads safely.
  163. exists = [[NSFileManager defaultManager] fileExistsAtPath:[self defaultCachePathForKey:key]];
  164. return exists;
  165. }
  166. - (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
  167. dispatch_async(_ioQueue, ^{
  168. BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
  169. if (completionBlock) {
  170. dispatch_async(dispatch_get_main_queue(), ^{
  171. completionBlock(exists);
  172. });
  173. }
  174. });
  175. }
  176. - (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
  177. return [self.memCache objectForKey:key];
  178. }
  179. - (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
  180. // First check the in-memory cache...
  181. UIImage *image = [self imageFromMemoryCacheForKey:key];
  182. if (image) {
  183. return image;
  184. }
  185. // Second check the disk cache...
  186. UIImage *diskImage = [self diskImageForKey:key];
  187. if (diskImage) {
  188. CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale;
  189. [self.memCache setObject:diskImage forKey:key cost:cost];
  190. }
  191. return diskImage;
  192. }
  193. - (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key {
  194. NSString *defaultPath = [self defaultCachePathForKey:key];
  195. NSData *data = [NSData dataWithContentsOfFile:defaultPath];
  196. if (data) {
  197. return data;
  198. }
  199. for (NSString *path in self.customPaths) {
  200. NSString *filePath = [self cachePathForKey:key inPath:path];
  201. NSData *imageData = [NSData dataWithContentsOfFile:filePath];
  202. if (imageData) {
  203. return imageData;
  204. }
  205. }
  206. return nil;
  207. }
  208. - (UIImage *)diskImageForKey:(NSString *)key {
  209. NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
  210. if (data) {
  211. UIImage *image = [UIImage sd_imageWithData:data];
  212. image = [self scaledImageForKey:key image:image];
  213. image = [UIImage decodedImageWithImage:image];
  214. return image;
  215. }
  216. else {
  217. return nil;
  218. }
  219. }
  220. - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
  221. return SDScaledImageForKey(key, image);
  222. }
  223. - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
  224. if (!doneBlock) {
  225. return nil;
  226. }
  227. if (!key) {
  228. doneBlock(nil, SDImageCacheTypeNone);
  229. return nil;
  230. }
  231. // First check the in-memory cache...
  232. UIImage *image = [self imageFromMemoryCacheForKey:key];
  233. if (image) {
  234. doneBlock(image, SDImageCacheTypeMemory);
  235. return nil;
  236. }
  237. NSOperation *operation = [NSOperation new];
  238. dispatch_async(self.ioQueue, ^{
  239. if (operation.isCancelled) {
  240. return;
  241. }
  242. @autoreleasepool {
  243. UIImage *diskImage = [self diskImageForKey:key];
  244. if (diskImage) {
  245. CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale;
  246. [self.memCache setObject:diskImage forKey:key cost:cost];
  247. }
  248. dispatch_async(dispatch_get_main_queue(), ^{
  249. doneBlock(diskImage, SDImageCacheTypeDisk);
  250. });
  251. }
  252. });
  253. return operation;
  254. }
  255. - (void)removeImageForKey:(NSString *)key {
  256. [self removeImageForKey:key withCompletion:nil];
  257. }
  258. - (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion {
  259. [self removeImageForKey:key fromDisk:YES withCompletion:completion];
  260. }
  261. - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk {
  262. [self removeImageForKey:key fromDisk:fromDisk withCompletion:nil];
  263. }
  264. - (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
  265. if (key == nil) {
  266. return;
  267. }
  268. [self.memCache removeObjectForKey:key];
  269. if (fromDisk) {
  270. dispatch_async(self.ioQueue, ^{
  271. [_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
  272. if (completion) {
  273. dispatch_async(dispatch_get_main_queue(), ^{
  274. completion();
  275. });
  276. }
  277. });
  278. } else if (completion){
  279. completion();
  280. }
  281. }
  282. - (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
  283. self.memCache.totalCostLimit = maxMemoryCost;
  284. }
  285. - (NSUInteger)maxMemoryCost {
  286. return self.memCache.totalCostLimit;
  287. }
  288. - (void)clearMemory {
  289. [self.memCache removeAllObjects];
  290. }
  291. - (void)clearDisk {
  292. [self clearDiskOnCompletion:nil];
  293. }
  294. - (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
  295. {
  296. dispatch_async(self.ioQueue, ^{
  297. [_fileManager removeItemAtPath:self.diskCachePath error:nil];
  298. [_fileManager createDirectoryAtPath:self.diskCachePath
  299. withIntermediateDirectories:YES
  300. attributes:nil
  301. error:NULL];
  302. if (completion) {
  303. dispatch_async(dispatch_get_main_queue(), ^{
  304. completion();
  305. });
  306. }
  307. });
  308. }
  309. - (void)cleanDisk {
  310. [self cleanDiskWithCompletionBlock:nil];
  311. }
  312. - (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
  313. dispatch_async(self.ioQueue, ^{
  314. NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
  315. NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
  316. // This enumerator prefetches useful properties for our cache files.
  317. NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
  318. includingPropertiesForKeys:resourceKeys
  319. options:NSDirectoryEnumerationSkipsHiddenFiles
  320. errorHandler:NULL];
  321. NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
  322. NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
  323. NSUInteger currentCacheSize = 0;
  324. // Enumerate all of the files in the cache directory. This loop has two purposes:
  325. //
  326. // 1. Removing files that are older than the expiration date.
  327. // 2. Storing file attributes for the size-based cleanup pass.
  328. NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
  329. for (NSURL *fileURL in fileEnumerator) {
  330. NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
  331. // Skip directories.
  332. if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
  333. continue;
  334. }
  335. // Remove files that are older than the expiration date;
  336. NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
  337. if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
  338. [urlsToDelete addObject:fileURL];
  339. continue;
  340. }
  341. // Store a reference to this file and account for its total size.
  342. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
  343. currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
  344. [cacheFiles setObject:resourceValues forKey:fileURL];
  345. }
  346. for (NSURL *fileURL in urlsToDelete) {
  347. [_fileManager removeItemAtURL:fileURL error:nil];
  348. }
  349. // If our remaining disk cache exceeds a configured maximum size, perform a second
  350. // size-based cleanup pass. We delete the oldest files first.
  351. if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
  352. // Target half of our maximum cache size for this cleanup pass.
  353. const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
  354. // Sort the remaining cache files by their last modification time (oldest first).
  355. NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
  356. usingComparator:^NSComparisonResult(id obj1, id obj2) {
  357. return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
  358. }];
  359. // Delete files until we fall below our desired cache size.
  360. for (NSURL *fileURL in sortedFiles) {
  361. if ([_fileManager removeItemAtURL:fileURL error:nil]) {
  362. NSDictionary *resourceValues = cacheFiles[fileURL];
  363. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
  364. currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
  365. if (currentCacheSize < desiredCacheSize) {
  366. break;
  367. }
  368. }
  369. }
  370. }
  371. if (completionBlock) {
  372. dispatch_async(dispatch_get_main_queue(), ^{
  373. completionBlock();
  374. });
  375. }
  376. });
  377. }
  378. - (void)backgroundCleanDisk {
  379. UIApplication *application = [UIApplication sharedApplication];
  380. __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
  381. // Clean up any unfinished task business by marking where you
  382. // stopped or ending the task outright.
  383. [application endBackgroundTask:bgTask];
  384. bgTask = UIBackgroundTaskInvalid;
  385. }];
  386. // Start the long-running task and return immediately.
  387. [self cleanDiskWithCompletionBlock:^{
  388. [application endBackgroundTask:bgTask];
  389. bgTask = UIBackgroundTaskInvalid;
  390. }];
  391. }
  392. - (NSUInteger)getSize {
  393. __block NSUInteger size = 0;
  394. dispatch_sync(self.ioQueue, ^{
  395. NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
  396. for (NSString *fileName in fileEnumerator) {
  397. NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
  398. NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
  399. size += [attrs fileSize];
  400. }
  401. });
  402. return size;
  403. }
  404. - (NSUInteger)getDiskCount {
  405. __block NSUInteger count = 0;
  406. dispatch_sync(self.ioQueue, ^{
  407. NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
  408. count = [[fileEnumerator allObjects] count];
  409. });
  410. return count;
  411. }
  412. - (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock {
  413. NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
  414. dispatch_async(self.ioQueue, ^{
  415. NSUInteger fileCount = 0;
  416. NSUInteger totalSize = 0;
  417. NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
  418. includingPropertiesForKeys:@[NSFileSize]
  419. options:NSDirectoryEnumerationSkipsHiddenFiles
  420. errorHandler:NULL];
  421. for (NSURL *fileURL in fileEnumerator) {
  422. NSNumber *fileSize;
  423. [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
  424. totalSize += [fileSize unsignedIntegerValue];
  425. fileCount += 1;
  426. }
  427. if (completionBlock) {
  428. dispatch_async(dispatch_get_main_queue(), ^{
  429. completionBlock(fileCount, totalSize);
  430. });
  431. }
  432. });
  433. }
  434. @end