From 1abc05e05c617b398a08ad8653ba0db289a83aed Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 27 Aug 2019 17:40:05 +0800 Subject: [PATCH 1/4] Add new properties `clearBufferWhenStopped` and `resetFrameIndexWhenStopped` --- SDWebImage/Core/SDAnimatedImageView.h | 14 ++++++++++ SDWebImage/Core/SDAnimatedImageView.m | 37 +++++++++++++++++++-------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/SDWebImage/Core/SDAnimatedImageView.h b/SDWebImage/Core/SDAnimatedImageView.h index ec5bda13..abf4e5d7 100644 --- a/SDWebImage/Core/SDAnimatedImageView.h +++ b/SDWebImage/Core/SDAnimatedImageView.h @@ -58,6 +58,20 @@ */ @property (nonatomic, assign) BOOL shouldIncrementalLoad; +/** + Whether or not to clear the frame buffer cache when animation stopped. See `maxBufferSize` + This is useful when you want to limit the memory usage during frequently visibility changes (such as image view inside a list view, then push and pop) + Default is NO. + */ +@property (nonatomic, assign) BOOL clearBufferWhenStopped; + +/** + Whether or not to reset the current frame index when animation stopped. + For some of use case, you may want to reset the frame index to 0 when stop, but some other want to keep the current frame index. + Default is NO. + */ +@property (nonatomic, assign) BOOL resetFrameIndexWhenStopped; + #if SD_UIKIT /** You can specify a runloop mode to let it rendering. diff --git a/SDWebImage/Core/SDAnimatedImageView.m b/SDWebImage/Core/SDAnimatedImageView.m index 9c4dddd0..672d9086 100644 --- a/SDWebImage/Core/SDAnimatedImageView.m +++ b/SDWebImage/Core/SDAnimatedImageView.m @@ -149,21 +149,15 @@ static NSUInteger SDDeviceFreeMemory() { self.animatedImage = nil; self.totalFrameCount = 0; self.totalLoopCount = 0; - self.currentFrame = nil; - self.currentFrameIndex = 0; - self.currentLoopCount = 0; - self.currentTime = 0; - self.bufferMiss = NO; + // reset current state + [self resetCurrentFrameIndex]; self.shouldAnimate = NO; self.isProgressive = NO; self.maxBufferCount = 0; self.animatedImageScale = 1; [_fetchQueue cancelAllOperations]; - _fetchQueue = nil; - SD_LOCK(self.lock); - [_frameBuffer removeAllObjects]; - _frameBuffer = nil; - SD_UNLOCK(self.lock); + // clear buffer cache + [self clearFrameBuffer]; } - (void)resetProgressiveImage @@ -179,6 +173,23 @@ static NSUInteger SDDeviceFreeMemory() { // preserve buffer cache } +- (void)resetCurrentFrameIndex +{ + self.currentFrame = nil; + self.currentFrameIndex = 0; + self.currentLoopCount = 0; + self.currentTime = 0; + self.bufferMiss = NO; +} + +- (void)clearFrameBuffer +{ + SD_LOCK(self.lock); + [_frameBuffer removeAllObjects]; + _frameBuffer = nil; + SD_UNLOCK(self.lock); +} + #pragma mark - Accessors #pragma mark Public @@ -466,6 +477,12 @@ static NSUInteger SDDeviceFreeMemory() { #else _displayLink.paused = YES; #endif + if (self.resetFrameIndexWhenStopped) { + [self resetCurrentFrameIndex]; + } + if (self.clearBufferWhenStopped) { + [self clearFrameBuffer]; + } } else { #if SD_UIKIT [super stopAnimating]; From 1f74aea686d51d56c0d328f4a293a6ac2c871afe Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 27 Aug 2019 19:41:27 +0800 Subject: [PATCH 2/4] Add the test case for these two new properties --- Tests/Tests/SDAnimatedImageTest.m | 68 +++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/Tests/Tests/SDAnimatedImageTest.m b/Tests/Tests/SDAnimatedImageTest.m index 573bd5b3..6555c287 100644 --- a/Tests/Tests/SDAnimatedImageTest.m +++ b/Tests/Tests/SDAnimatedImageTest.m @@ -16,6 +16,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun @interface SDAnimatedImageView () @property (nonatomic, assign) BOOL isProgressive; +@property (nonatomic, strong) NSMutableDictionary *frameBuffer; @end @@ -206,6 +207,7 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun #else imageView.animates = NO; #endif + [imageView removeFromSuperview]; [expectation fulfill]; } }]; @@ -298,6 +300,72 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun [self waitForExpectationsWithCommonTimeout]; } +- (void)test25AnimatedImageStopAnimatingNormal { + XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView stopAnimating normal behavior"]; + + SDAnimatedImageView *imageView = [SDAnimatedImageView new]; + +#if SD_UIKIT + [self.window addSubview:imageView]; +#else + [self.window.contentView addSubview:imageView]; +#endif + // This APNG duration is 2s + SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]]; + imageView.image = image; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // 0.5s is not finished, frame index should not be 0 + expect(imageView.frameBuffer.count).beGreaterThan(0); + expect(imageView.currentFrameIndex).beGreaterThan(0); + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [imageView stopAnimating]; + expect(imageView.frameBuffer.count).beGreaterThan(0); + expect(imageView.currentFrameIndex).beGreaterThan(0); + + [imageView removeFromSuperview]; + [expectation fulfill]; + }); + + [self waitForExpectationsWithCommonTimeout]; +} + +- (void)test25AnimatedImageStopAnimatingClearBuffer { + XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView stopAnimating clear buffer when stopped"]; + + SDAnimatedImageView *imageView = [SDAnimatedImageView new]; + imageView.clearBufferWhenStopped = YES; + imageView.resetFrameIndexWhenStopped = YES; + +#if SD_UIKIT + [self.window addSubview:imageView]; +#else + [self.window.contentView addSubview:imageView]; +#endif + // This APNG duration is 2s + SDAnimatedImage *image = [SDAnimatedImage imageWithData:[self testAPNGPData]]; + imageView.image = image; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // 0.5s is not finished, frame index should not be 0 + expect(imageView.frameBuffer.count).beGreaterThan(0); + expect(imageView.currentFrameIndex).beGreaterThan(0); + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [imageView stopAnimating]; + expect(imageView.frameBuffer.count).equal(0); + expect(imageView.currentFrameIndex).equal(0); + + [imageView removeFromSuperview]; + [expectation fulfill]; + }); + + [self waitForExpectationsWithCommonTimeout]; +} + #pragma mark - Helper - (UIWindow *)window { if (!_window) { From dd8f58917f9bffb52762ee53d640c4a08ef15c49 Mon Sep 17 00:00:00 2001 From: kinarob Date: Wed, 28 Aug 2019 15:25:42 +0800 Subject: [PATCH 3/4] reuse frame buffer container --- SDWebImage/Core/SDAnimatedImageView.m | 1 - 1 file changed, 1 deletion(-) diff --git a/SDWebImage/Core/SDAnimatedImageView.m b/SDWebImage/Core/SDAnimatedImageView.m index 672d9086..0adcfd61 100644 --- a/SDWebImage/Core/SDAnimatedImageView.m +++ b/SDWebImage/Core/SDAnimatedImageView.m @@ -186,7 +186,6 @@ static NSUInteger SDDeviceFreeMemory() { { SD_LOCK(self.lock); [_frameBuffer removeAllObjects]; - _frameBuffer = nil; SD_UNLOCK(self.lock); } From 70b8fec54a683ea203605fc2f97137abf2c9fbd4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Fri, 30 Aug 2019 17:25:10 +0800 Subject: [PATCH 4/4] Fix the compile issue for test cases on macOS --- Tests/Tests/SDAnimatedImageTest.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/Tests/SDAnimatedImageTest.m b/Tests/Tests/SDAnimatedImageTest.m index 6555c287..cc04cd0d 100644 --- a/Tests/Tests/SDAnimatedImageTest.m +++ b/Tests/Tests/SDAnimatedImageTest.m @@ -321,7 +321,11 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +#if SD_UIKIT [imageView stopAnimating]; +#else + imageView.animates = NO; +#endif expect(imageView.frameBuffer.count).beGreaterThan(0); expect(imageView.currentFrameIndex).beGreaterThan(0); @@ -355,7 +359,11 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +#if SD_UIKIT [imageView stopAnimating]; +#else + imageView.animates = NO; +#endif expect(imageView.frameBuffer.count).equal(0); expect(imageView.currentFrameIndex).equal(0);