Skip to content

Commit 3fbb49c

Browse files
committed
Update the latest YYCache to match SDImageCache's behavior
1 parent 7066b91 commit 3fbb49c

File tree

1 file changed

+180
-163
lines changed

1 file changed

+180
-163
lines changed

SDWebImageYYPlugin/Classes/YYCache/YYCacheBridge/YYCache+SDAdditions.m

Lines changed: 180 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,66 @@
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

Comments
 (0)