1010#import " YYMemoryCache+SDAdditions.h"
1111#import " YYDiskCache+SDAdditions.h"
1212
13- static NSData * SDYYPluginCacheDataWithImageData (UIImage *image, NSData *imageData) {
14- NSData *data = imageData;
15- if (!data && [image conformsToProtocol: @protocol (SDAnimatedImage)]) {
16- // If image is custom animated image class, prefer its original animated data
17- data = [((id <SDAnimatedImage>)image) animatedImageData ];
13+ static void SDYYPluginUnarchiveObject (NSData *data, UIImage *image) {
14+ if (!data || !image) {
15+ return ;
1816 }
19- if (!data && image) {
20- // Check image's associated image format, may return .undefined
21- SDImageFormat format = image.sd_imageFormat ;
22- if (format == SDImageFormatUndefined) {
23- // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
24- if (image.sd_isAnimated ) {
25- format = SDImageFormatGIF;
26- } else {
27- // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
28- if ([SDImageCoderHelper CGImageContainsAlpha: image.CGImage]) {
29- format = SDImageFormatPNG;
30- } else {
31- format = SDImageFormatJPEG;
32- }
33- }
17+ // Check extended data
18+ NSData *extendedData = [YYDiskCache getExtendedDataFromObject: data];
19+ if (!extendedData) {
20+ return ;
21+ }
22+ id extendedObject;
23+ if (@available (iOS 11 , tvOS 11 , macOS 10.13 , watchOS 4 , *)) {
24+ NSError *error;
25+ NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc ] initForReadingFromData: extendedData error: &error];
26+ unarchiver.requiresSecureCoding = NO ;
27+ extendedObject = [unarchiver decodeTopLevelObjectForKey: NSKeyedArchiveRootObjectKey error: &error];
28+ if (error) {
29+ NSLog (@" NSKeyedUnarchiver unarchive failed with error: %@ " , error);
30+ }
31+ } else {
32+ @try {
33+ #pragma clang diagnostic push
34+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
35+ extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData: extendedData];
36+ #pragma clang diagnostic pop
37+ } @catch (NSException *exception) {
38+ NSLog (@" NSKeyedUnarchiver unarchive failed with exception: %@ " , exception);
3439 }
35- data = [[SDImageCodersManager sharedManager ] encodedDataWithImage: image format: format options: nil ];
3640 }
37-
38- return data;
41+ image.sd_extendedObject = extendedObject;
42+ }
43+
44+ static void SDYYPluginArchiveObject (NSData *data, UIImage *image) {
45+ if (!data || !image) {
46+ return ;
47+ }
48+ // Check extended data
49+ id extendedObject = image.sd_extendedObject ;
50+ if (![extendedObject conformsToProtocol: @protocol (NSCoding)]) {
51+ return ;
52+ }
53+ NSData *extendedData;
54+ if (@available (iOS 11 , tvOS 11 , macOS 10.13 , watchOS 4 , *)) {
55+ NSError *error;
56+ extendedData = [NSKeyedArchiver archivedDataWithRootObject: extendedObject requiringSecureCoding: NO error: &error];
57+ if (error) {
58+ NSLog (@" NSKeyedArchiver archive failed with error: %@ " , error);
59+ }
60+ } else {
61+ @try {
62+ #pragma clang diagnostic push
63+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
64+ extendedData = [NSKeyedArchiver archivedDataWithRootObject: extendedObject];
65+ #pragma clang diagnostic pop
66+ } @catch (NSException *exception) {
67+ NSLog (@" NSKeyedArchiver archive failed with exception: %@ " , exception);
68+ }
69+ }
70+ if (extendedData) {
71+ [YYDiskCache setExtendedData: extendedData toObject: data];
72+ }
3973}
4074
4175@implementation YYCache (SDAdditions)
@@ -68,8 +102,7 @@ @implementation YYCache (SDAdditions)
68102 if (image) {
69103 if (options & SDImageCacheDecodeFirstFrameOnly) {
70104 // Ensure static image
71- Class animatedImageClass = image.class ;
72- if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass: [UIImage class ]] && [animatedImageClass conformsToProtocol: @protocol (SDAnimatedImage)])) {
105+ if (image.sd_isAnimated ) {
73106#if SD_MAC
74107 image = [[NSImage alloc ] initWithCGImage: image.CGImage scale: image.scale orientation: kCGImagePropertyOrientationUp ];
75108#else
@@ -95,176 +128,160 @@ @implementation YYCache (SDAdditions)
95128 }
96129
97130 // Second check the disk cache...
131+ SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
98132 NSOperation *operation = [NSOperation new ];
99133 // Check whether we need to synchronously query disk
100134 // 1. in-memory cache hit & memoryDataSync
101135 // 2. in-memory cache miss & diskDataSync
102136 BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
103137 (!image && options & SDImageCacheQueryDiskDataSync));
104- void (^queryDiskBlock)( NSData * ) = ^( NSData *diskData) {
105- if (operation. isCancelled ) {
106- if (doneBlock ) {
107- doneBlock ( nil , nil , SDImageCacheTypeNone) ;
138+ NSData * (^queryDiskDataBlock)( void ) = ^NSData * {
139+ @synchronized (operation) {
140+ if (operation. isCancelled ) {
141+ return nil ;
108142 }
109- return ;
110143 }
111144
112- @autoreleasepool {
113- UIImage *diskImage;
114- if (image) {
115- // the image is from in-memory cache, but need image data
116- diskImage = image;
117- } else if (diskData) {
118- BOOL shouldCacheToMomery = YES ;
119- if (context[SDWebImageContextStoreCacheType]) {
120- SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue ];
121- shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
122- }
123- // decode image data only if in-memory cache missed
124- diskImage = SDImageCacheDecodeImageData (diskData, key, options, context);
125- if (diskImage) {
126- // Check extended data
127- NSData *extendedData = [YYDiskCache getExtendedDataFromObject: diskData];
128- if (extendedData) {
129- id extendedObject;
130- if (@available (iOS 11 , tvOS 11 , macOS 10.13 , watchOS 4 , *)) {
131- NSError *error;
132- NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc ] initForReadingFromData: extendedData error: &error];
133- unarchiver.requiresSecureCoding = NO ;
134- extendedObject = [unarchiver decodeTopLevelObjectForKey: NSKeyedArchiveRootObjectKey error: &error];
135- if (error) {
136- NSLog (@" NSKeyedUnarchiver unarchive failed with error: %@ " , error);
137- }
138- } else {
139- @try {
140- #pragma clang diagnostic push
141- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
142- extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData: extendedData];
143- #pragma clang diagnostic pop
144- } @catch (NSException *exception) {
145- NSLog (@" NSKeyedUnarchiver unarchive failed with exception: %@ " , exception);
146- }
147- }
148- diskImage.sd_extendedObject = extendedObject;
149- }
150- }
151- if (shouldCacheToMomery && diskImage) {
152- NSUInteger cost = diskImage.sd_memoryCost ;
153- [self .memoryCache setObject: diskImage forKey: key cost: cost];
154- }
145+ return [self .diskCache dataForKey: key];
146+ };
147+ UIImage* (^queryDiskImageBlock)(NSData *) = ^UIImage*(NSData * diskData) {
148+ @synchronized (operation) {
149+ if (operation.isCancelled ) {
150+ return nil ;
155151 }
156-
157- if (doneBlock) {
158- if (shouldQueryDiskSync) {
159- doneBlock (diskImage, diskData, SDImageCacheTypeDisk);
160- } else {
161- dispatch_async (dispatch_get_main_queue (), ^{
162- doneBlock (diskImage, diskData, SDImageCacheTypeDisk);
163- });
164- }
152+ }
153+
154+ UIImage *diskImage;
155+ if (image) {
156+ // the image is from in-memory cache, but need image data
157+ diskImage = image;
158+ } else if (diskData) {
159+ BOOL shouldCacheToMomery = YES ;
160+ if (context[SDWebImageContextStoreCacheType]) {
161+ SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue ];
162+ shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);
163+ }
164+ CGSize thumbnailSize = CGSizeZero;
165+ NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
166+ if (thumbnailSizeValue != nil ) {
167+ #if SD_MAC
168+ thumbnailSize = thumbnailSizeValue.sizeValue ;
169+ #else
170+ thumbnailSize = thumbnailSizeValue.CGSizeValue ;
171+ #endif
172+ }
173+ if (thumbnailSize.width > 0 && thumbnailSize.height > 0 ) {
174+ // Query full size cache key which generate a thumbnail, should not write back to full size memory cache
175+ shouldCacheToMomery = NO ;
176+ }
177+ // decode image data only if in-memory cache missed
178+ diskImage = SDImageCacheDecodeImageData (diskData, key, options, context);
179+ SDYYPluginUnarchiveObject (diskData, diskImage);
180+ if (shouldCacheToMomery && diskImage) {
181+ NSUInteger cost = diskImage.sd_memoryCost ;
182+ [self .memoryCache setObject: diskImage forKey: key cost: cost];
165183 }
166184 }
185+ return diskImage;
167186 };
168187
188+ // YYCache has its own IO queue
169189 if (shouldQueryDiskSync) {
170- NSData *diskData = [self .diskCache dataForKey: key];
171- queryDiskBlock (diskData);
190+ NSData * diskData = queryDiskDataBlock ();
191+ UIImage* diskImage = queryDiskImageBlock (diskData);
192+ if (doneBlock) {
193+ doneBlock (diskImage, diskData, SDImageCacheTypeDisk);
194+ }
172195 } else {
173- // YYDiskCache's completion block is called in the global queue
174- [self .diskCache objectForKey: key withBlock: ^(NSString * _Nonnull key, id <NSCoding > _Nullable object) {
175- NSData *diskData = nil ;
176- if ([(id <NSObject >)object isKindOfClass: [NSData class ]]) {
177- diskData = (NSData *)object;
196+ dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{
197+ NSData * diskData = queryDiskDataBlock ();
198+ UIImage* diskImage = queryDiskImageBlock (diskData);
199+ @synchronized (operation) {
200+ if (operation.isCancelled ) {
201+ return ;
202+ }
178203 }
179- queryDiskBlock (diskData);
180- }];
204+ if (doneBlock) {
205+ [(queue ?: SDCallbackQueue.mainQueue) async: ^{
206+ // Dispatch from IO queue to main queue need time, user may call cancel during the dispatch timing
207+ // This check is here to avoid double callback (one is from `SDImageCacheToken` in sync)
208+ @synchronized (operation) {
209+ if (operation.isCancelled ) {
210+ return ;
211+ }
212+ }
213+ doneBlock (diskImage, diskData, SDImageCacheTypeDisk);
214+ }];
215+ }
216+ });
181217 }
182218
183219 return operation;
184220}
185221
186- - (void )storeImageToDisk : (UIImage *)image imageData : (NSData *)imageData forKey : (NSString *)key completion : (SDWebImageNoParamsBlock)completionBlock {
187- NSData *data = SDYYPluginCacheDataWithImageData (image, imageData);
188- if (!data) {
189- // SDImageCache does not remove object if `data` is nil
222+ - (void )storeImage : (UIImage *)image imageData : (NSData *)imageData forKey : (NSString *)key cacheType : (SDImageCacheType)cacheType completion : (SDWebImageNoParamsBlock)completionBlock {
223+ [self storeImage: image imageData: imageData forKey: key options: 0 context: nil cacheType: cacheType completion: completionBlock];
224+ }
225+
226+ - (void )storeImage : (UIImage *)image imageData : (NSData *)imageData forKey : (NSString *)key options : (SDWebImageOptions)options context : (SDWebImageContext *)context cacheType : (SDImageCacheType)cacheType completion : (SDWebImageNoParamsBlock)completionBlock {
227+ if ((!image && !imageData) || !key) {
190228 if (completionBlock) {
191- dispatch_async (dispatch_get_main_queue (), ^{
192- completionBlock ();
193- });
229+ completionBlock ();
194230 }
195231 return ;
196232 }
197- if (image) {
198- // Check extended data
199- id extendedObject = image.sd_extendedObject ;
200- if ([extendedObject conformsToProtocol: @protocol (NSCoding)]) {
201- NSData *extendedData;
202- if (@available (iOS 11 , tvOS 11 , macOS 10.13 , watchOS 4 , *)) {
203- NSError *error;
204- extendedData = [NSKeyedArchiver archivedDataWithRootObject: extendedObject requiringSecureCoding: NO error: &error];
205- if (error) {
206- NSLog (@" NSKeyedArchiver archive failed with error: %@ " , error);
207- }
208- } else {
209- @try {
210- #pragma clang diagnostic push
211- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
212- extendedData = [NSKeyedArchiver archivedDataWithRootObject: extendedObject];
213- #pragma clang diagnostic pop
214- } @catch (NSException *exception) {
215- NSLog (@" NSKeyedArchiver archive failed with exception: %@ " , exception);
216- }
217- }
218- if (extendedData) {
219- [YYDiskCache setExtendedData: extendedData toObject: data];
220- }
221- }
233+ BOOL toMemory = cacheType == SDImageCacheTypeMemory || cacheType == SDImageCacheTypeAll;
234+ BOOL toDisk = cacheType == SDImageCacheTypeDisk || cacheType == SDImageCacheTypeAll;
235+ // if memory cache is enabled
236+ if (image && toMemory) {
237+ NSUInteger cost = image.sd_memoryCost ;
238+ [self .memoryCache setObject: image forKey: key cost: cost];
222239 }
223- [self .diskCache setObject: data forKey: key withBlock: ^{
240+
241+ if (!toDisk) {
224242 if (completionBlock) {
225- dispatch_async (dispatch_get_main_queue (), ^{
226- completionBlock ();
227- });
243+ completionBlock ();
228244 }
229- }];
230- }
231-
232- - (void )storeImage : (UIImage *)image imageData : (NSData *)imageData forKey : (NSString *)key cacheType : (SDImageCacheType)cacheType completion : (SDWebImageNoParamsBlock)completionBlock {
233- switch (cacheType) {
234- case SDImageCacheTypeNone: {
235- if (completionBlock) {
236- completionBlock ();
237- }
238- }
239- break ;
240- case SDImageCacheTypeMemory: {
241- NSUInteger cost = image.sd_memoryCost ;
242- [self .memoryCache setObject: image forKey: key cost: cost];
243- if (completionBlock) {
244- completionBlock ();
245+ return ;
246+ }
247+ NSData *data = imageData;
248+ if (!data && [image respondsToSelector: @selector (animatedImageData )]) {
249+ // If image is custom animated image class, prefer its original animated data
250+ data = [((id <SDAnimatedImage>)image) animatedImageData ];
251+ }
252+ SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
253+ if (!data && image) {
254+ dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{
255+ // Check image's associated image format, may return .undefined
256+ SDImageFormat format = image.sd_imageFormat ;
257+ if (format == SDImageFormatUndefined) {
258+ // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
259+ if (image.sd_isAnimated ) {
260+ format = SDImageFormatGIF;
261+ } else {
262+ // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
263+ format = [SDImageCoderHelper CGImageContainsAlpha: image.CGImage] ? SDImageFormatPNG : SDImageFormatJPEG;
264+ }
245265 }
246- }
247- break ;
248- case SDImageCacheTypeDisk: {
249- dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{
250- [self storeImageToDisk: image imageData: imageData forKey: key completion: completionBlock];
251- });
252- }
253- break ;
254- case SDImageCacheTypeAll: {
255- NSUInteger cost = image.sd_memoryCost ;
256- [self .memoryCache setObject: image forKey: key cost: cost];
257- dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{
258- [self storeImageToDisk: image imageData: imageData forKey: key completion: completionBlock];
259- });
260- }
261- break ;
262- default : {
266+ NSData *data = [[SDImageCodersManager sharedManager ] encodedDataWithImage: image format: format options: context[SDWebImageContextImageEncodeOptions]];
267+ SDYYPluginArchiveObject (data, image);
268+ [self .diskCache setObject: data forKey: key withBlock: ^{
269+ if (completionBlock) {
270+ [(queue ?: SDCallbackQueue.mainQueue) async: ^{
271+ completionBlock ();
272+ }];
273+ }
274+ }];
275+ });
276+ } else {
277+ SDYYPluginArchiveObject (data, image);
278+ [self .diskCache setObject: data forKey: key withBlock: ^{
263279 if (completionBlock) {
264- completionBlock ();
280+ [(queue ?: SDCallbackQueue.mainQueue) async: ^{
281+ completionBlock ();
282+ }];
265283 }
266- }
267- break ;
284+ }];
268285 }
269286}
270287
0 commit comments