Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't encode/decode image when app will terminated #3149

Merged
merged 6 commits into from Apr 1, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions SDWebImage/Core/SDImageCodersManager.m
Expand Up @@ -41,16 +41,14 @@ - (instancetype)init {
return self;
}

- (NSArray<id<SDImageCoder>> *)coders
{
- (NSArray<id<SDImageCoder>> *)coders {
SD_LOCK(_codersLock);
NSArray<id<SDImageCoder>> *coders = [_imageCoders copy];
SD_UNLOCK(_codersLock);
return coders;
}

- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders
{
- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders {
SD_LOCK(_codersLock);
[_imageCoders removeAllObjects];
if (coders.count) {
Expand Down Expand Up @@ -104,6 +102,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
if (!data) {
return nil;
}

UIImage *image;
NSArray<id<SDImageCoder>> *coders = self.coders;
for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
Expand All @@ -120,6 +119,7 @@ - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format o
if (!image) {
return nil;
}

NSArray<id<SDImageCoder>> *coders = self.coders;
for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
if ([coder canEncodeToFormat:format]) {
Expand Down
65 changes: 57 additions & 8 deletions SDWebImage/Core/SDImageIOAnimatedCoder.m
Expand Up @@ -29,6 +29,9 @@ @interface SDImageIOCoderFrame : NSObject
@implementation SDImageIOCoderFrame
@end

BOOL willTerminated_;
NSLock *terminatedLock_;
dreampiggy marked this conversation as resolved.
Show resolved Hide resolved

@implementation SDImageIOAnimatedCoder {
size_t _width, _height;
CGImageSourceRef _imageSource;
Expand All @@ -42,8 +45,40 @@ @implementation SDImageIOAnimatedCoder {
CGSize _thumbnailSize;
}

- (void)dealloc
{
+ (void)initialize {
if (self == SDImageIOAnimatedCoder.class) {
willTerminated_ = NO;
terminatedLock_ = [[NSLock alloc] init];
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification
object:nil];

#endif
#if SD_MAC
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
#endif
}
}

+ (void)applicationWillTerminate:(NSNotification *)note {
[terminatedLock_ lock];
willTerminated_ = YES;
[terminatedLock_ unlock];
}

+ (BOOL)willTerminated {
[terminatedLock_ lock];
BOOL willTerminated = willTerminated_;
[terminatedLock_ unlock];
return willTerminated;
}

- (void)dealloc {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
Expand All @@ -53,8 +88,7 @@ - (void)dealloc
#endif
}

- (void)didReceiveMemoryWarning:(NSNotification *)notification
{
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
if (_imageSource) {
for (size_t i = 0; i < _frameCount; i++) {
CGImageSourceRemoveCacheAtIndex(_imageSource, i);
Expand Down Expand Up @@ -152,11 +186,17 @@ + (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
}

+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
NSTimeInterval frameDuration = 0.1;

// Earily return when application will be terminated.
if (self.willTerminated) {
kinarobin marked this conversation as resolved.
Show resolved Hide resolved
return frameDuration;
}

NSDictionary *options = @{
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
};
NSTimeInterval frameDuration = 0.1;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
if (!cfFrameProperties) {
return frameDuration;
Expand Down Expand Up @@ -188,6 +228,11 @@ + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRe
}

+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options {
// Earily return when application will be terminated.
if (self.willTerminated) {
return nil;
}

// Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
// Parse the image properties
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
Expand Down Expand Up @@ -375,11 +420,11 @@ - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)optio
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
#endif
}
_thumbnailSize = thumbnailSize;
BOOL preserveAspectRatio = YES;
Expand All @@ -399,6 +444,10 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
if (_finished) {
return;
}
// Earily return when application will be terminated.
if (self.class.willTerminated) {
return;
}
_imageData = data;
_finished = finished;

Expand Down
20 changes: 20 additions & 0 deletions Tests/Tests/SDImageCoderTests.m
Expand Up @@ -311,6 +311,26 @@ - (void)test21ThatEmbedThumbnailHEICWorks {
}
}

- (void)test22DoNotDecodeImageWhenApplicationWillTerminate {
[[SDImageCodersManager sharedManager] addCoder:SDImageIOCoder.sharedCoder];
XCTestExpectation *expectation = [self expectationWithDescription:@"doNotDecodeImageWhenApplicationWillTerminate"];
NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"png"];
NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
[[SDImageCache sharedImageCache] storeImageDataToDisk:testImageData forKey:@"TestImageLarge"];
NSOperation *operation = [[SDImageCache sharedImageCache] queryCacheOperationForKey:@"TestImageLarge" done:^(UIImage *image, NSData *data, SDImageCacheType cacheType) {
expect(data).to.equal(testImageData);
expect(image).to.beNil;
[[SDImageCache sharedImageCache] removeImageForKey:@"TestImageLarge" withCompletion:^{
[expectation fulfill];
}];
}];
expect(operation).toNot.beNil;
[operation start];
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillTerminateNotification object:nil];

[self waitForExpectationsWithCommonTimeout];
}

#pragma mark - Utils

- (void)verifyCoder:(id<SDImageCoder>)coder
Expand Down