Merge pull request #3524 from dreampiggy/feature/sdanimatedimage_frame_pool

Performance: Introduce frame pool for SDAnimatedImage playback. Solve when multiple image view references the same URL image cause un-wanted decode which waste RAM/CPU
This commit is contained in:
DreamPiggy 2023-06-04 12:38:52 +08:00 committed by GitHub
commit c27e18506d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 257 additions and 108 deletions

View File

@ -45,6 +45,9 @@
321E60C01F38E91700405457 /* UIImage+ForceDecode.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */; settings = {ATTRIBUTES = (Public, ); }; };
321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */; };
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */; };
3237321429F8D0D600D1DA41 /* SDImageFramePool.h in Headers */ = {isa = PBXBuildFile; fileRef = 3237321229F8D0D600D1DA41 /* SDImageFramePool.h */; settings = {ATTRIBUTES = (Private, ); }; };
3237321529F8D0D600D1DA41 /* SDImageFramePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 3237321329F8D0D600D1DA41 /* SDImageFramePool.m */; };
3237321629F8D0E200D1DA41 /* SDImageFramePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 3237321329F8D0D600D1DA41 /* SDImageFramePool.m */; };
3237F9E820161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
3237F9EB20161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
@ -407,6 +410,8 @@
321E60A11F38E8F600405457 /* SDImageGIFCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageGIFCoder.m; path = Core/SDImageGIFCoder.m; sourceTree = "<group>"; };
321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ForceDecode.h"; path = "Core/UIImage+ForceDecode.h"; sourceTree = "<group>"; };
321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ForceDecode.m"; path = "Core/UIImage+ForceDecode.m"; sourceTree = "<group>"; };
3237321229F8D0D600D1DA41 /* SDImageFramePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDImageFramePool.h; sourceTree = "<group>"; };
3237321329F8D0D600D1DA41 /* SDImageFramePool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDImageFramePool.m; sourceTree = "<group>"; };
3240BB6623968FE6003BA07D /* SDAssociatedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAssociatedObject.h; sourceTree = "<group>"; };
3240BB6723968FE6003BA07D /* SDAssociatedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAssociatedObject.m; sourceTree = "<group>"; };
324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = "<group>"; };
@ -691,6 +696,8 @@
325C460122339330004CAE11 /* SDImageAssetManager.m */,
325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */,
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */,
3237321229F8D0D600D1DA41 /* SDImageFramePool.h */,
3237321329F8D0D600D1DA41 /* SDImageFramePool.m */,
32C78E39233371AD00C6B7F8 /* SDImageIOAnimatedCoderInternal.h */,
3253F235244982D3006C2BE8 /* SDWebImageTransitionInternal.h */,
325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */,
@ -909,6 +916,7 @@
32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */,
328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */,
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */,
3237321429F8D0D600D1DA41 /* SDImageFramePool.h in Headers */,
325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */,
3253F236244982D3006C2BE8 /* SDWebImageTransitionInternal.h in Headers */,
321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */,
@ -1188,6 +1196,7 @@
32F21B5920788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */,
321B37952083290E00C0EA77 /* SDImageLoadersManager.m in Sources */,
4A2CAE361AB4BB7500B6BC39 /* UIImageView+WebCache.m in Sources */,
3237321529F8D0D600D1DA41 /* SDImageFramePool.m in Sources */,
4A2CAE1E1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.m in Sources */,
3298655E2337230C0071958B /* SDImageHEICCoder.m in Sources */,
32F7C0802030719600873181 /* UIImage+Transform.m in Sources */,
@ -1262,6 +1271,7 @@
32C0FDE72013426C001B8F2D /* SDWebImageIndicator.m in Sources */,
32B5CC63222F8B70005EB74E /* SDAsyncBlockOperation.m in Sources */,
32F21B5720788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */,
3237321629F8D0E200D1DA41 /* SDImageFramePool.m in Sources */,
5376130B155AD0D5005750A4 /* SDWebImageDownloader.m in Sources */,
321B37932083290E00C0EA77 /* SDImageLoadersManager.m in Sources */,
32F7C07E2030719600873181 /* UIImage+Transform.m in Sources */,

View File

@ -10,24 +10,24 @@
#import "NSImage+Compatibility.h"
#import "SDDisplayLink.h"
#import "SDDeviceHelper.h"
#import "SDImageFramePool.h"
#import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () {
SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode;
}
@property (nonatomic, strong) SDImageFramePool *framePool;
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, strong) id<SDAnimatedImageProvider> animatedProvider;
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
@property (nonatomic, assign) NSUInteger currentFrameBytes;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) BOOL shouldReverse;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) SDDisplayLink *displayLink;
@end
@ -47,10 +47,7 @@
self.totalLoopCount = provider.animatedImageLoopCount;
self.animatedProvider = provider;
self.playbackRate = 1.0;
SD_LOCK_INIT(_lock);
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
self.framePool = [SDImageFramePool registerProvider:provider];
}
return self;
}
@ -60,47 +57,12 @@
return player;
}
#pragma mark - Life Cycle
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[_fetchQueue cancelAllOperations];
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSNumber *currentFrameIndex = @(self.currentFrameIndex);
SD_LOCK(self->_lock);
NSArray *keys = self.frameBuffer.allKeys;
// only keep the next frame for later rendering
for (NSNumber * key in keys) {
if (![key isEqualToNumber:currentFrameIndex]) {
[self.frameBuffer removeObjectForKey:key];
}
}
SD_UNLOCK(self->_lock);
}];
[_fetchQueue addOperation:operation];
// Dereference the frame pool, when zero the frame pool for provider will dealloc
[SDImageFramePool unregisterProvider:self.animatedProvider];
}
#pragma mark - Private
- (NSOperationQueue *)fetchQueue {
if (!_fetchQueue) {
_fetchQueue = [[NSOperationQueue alloc] init];
_fetchQueue.maxConcurrentOperationCount = 1;
_fetchQueue.name = @"com.hackemist.SDAnimatedImagePlayer.fetchQueue";
}
return _fetchQueue;
}
- (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer {
if (!_frameBuffer) {
_frameBuffer = [NSMutableDictionary dictionary];
}
return _frameBuffer;
}
- (SDDisplayLink *)displayLink {
if (!_displayLink) {
@ -153,11 +115,11 @@
UIImage *posterFrame = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
if (posterFrame) {
// Calculate max buffer size
[self calculateMaxBufferCountWithFrame:posterFrame];
// HACK: The first frame should not check duration and immediately display
self.needsDisplayWhenImageBecomesAvailable = YES;
SD_LOCK(self->_lock);
self.frameBuffer[@(self.currentFrameIndex)] = posterFrame;
SD_UNLOCK(self->_lock);
[self.framePool setFrame:posterFrame atIndex:self.currentFrameIndex];
}
}
@ -174,9 +136,7 @@
}
- (void)clearFrameBuffer {
SD_LOCK(_lock);
[_frameBuffer removeAllObjects];
SD_UNLOCK(_lock);
[self.framePool removeAllFrames];
}
#pragma mark - Animation Control
@ -184,12 +144,9 @@
[self.displayLink start];
// Setup frame
[self setupCurrentFrame];
// Calculate max buffer size
[self calculateMaxBufferCount];
}
- (void)stopPlaying {
[_fetchQueue cancelAllOperations];
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
[_displayLink stop];
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
@ -197,7 +154,6 @@
}
- (void)pausePlaying {
[_fetchQueue cancelAllOperations];
[_displayLink stop];
}
@ -259,26 +215,11 @@
// Check if we need to display new frame firstly
BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame;
SD_LOCK(_lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)];
SD_UNLOCK(_lock);
UIImage *currentFrame = [self.framePool frameAtIndex:currentFrameIndex];
// Update the current frame
if (currentFrame) {
SD_LOCK(_lock);
// Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil;
}
// Check whether we can stop fetch
if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES;
}
SD_UNLOCK(_lock);
// Update the current frame immediately
self.currentFrame = currentFrame;
[self handleFrameChange];
@ -300,8 +241,7 @@
if (self.currentTime < currentDuration) {
// Current frame timestamp not reached, prefetch frame in advance.
[self prefetchFrameAtIndex:currentFrameIndex
nextIndex:nextFrameIndex
bufferFull:bufferFull];
nextIndex:nextFrameIndex];
return;
}
@ -337,43 +277,29 @@
}
[self prefetchFrameAtIndex:currentFrameIndex
nextIndex:nextFrameIndex
bufferFull:bufferFull];
nextIndex:nextFrameIndex];
}
// Check if we should prefetch next frame or current frame
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
- (void)prefetchFrameAtIndex:(NSUInteger)currentIndex
nextIndex:(NSUInteger)nextIndex
bufferFull:(BOOL)bufferFull {
nextIndex:(NSUInteger)nextIndex {
NSUInteger fetchFrameIndex = currentIndex;
UIImage *fetchFrame = nil;
if (!self.bufferMiss) {
fetchFrameIndex = nextIndex;
SD_LOCK(_lock);
fetchFrame = self.frameBuffer[@(nextIndex)];
SD_UNLOCK(_lock);
fetchFrame = [self.framePool frameAtIndex:nextIndex];
}
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
id<SDAnimatedImageProvider> animatedProvider = self.animatedProvider;
@weakify(self);
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
@strongify(self);
if (!self) {
return;
}
UIImage *frame = [animatedProvider animatedImageFrameAtIndex:fetchFrameIndex];
BOOL isAnimating = self.displayLink.isRunning;
if (isAnimating) {
SD_LOCK(self->_lock);
self.frameBuffer[@(fetchFrameIndex)] = frame;
SD_UNLOCK(self->_lock);
}
}];
[self.fetchQueue addOperation:operation];
BOOL bufferFull = NO;
if (self.framePool.currentFrameCount == self.totalFrameCount) {
bufferFull = YES;
}
if (!fetchFrame && !bufferFull) {
// Calculate max buffer size
[self calculateMaxBufferCountWithFrame:self.currentFrame];
// Prefetch next frame
[self.framePool prefetchFrameAtIndex:fetchFrameIndex];
}
}
@ -390,9 +316,17 @@
}
#pragma mark - Util
- (void)calculateMaxBufferCount {
NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
if (bytes == 0) bytes = 1024;
- (void)calculateMaxBufferCountWithFrame:(nonnull UIImage *)frame {
NSUInteger bytes = self.currentFrameBytes;
if (bytes == 0) {
bytes = CGImageGetBytesPerRow(frame.CGImage) * CGImageGetHeight(frame.CGImage);
if (bytes == 0) {
bytes = 1024;
} else {
// Cache since most animated image each frame bytes is the same
self.currentFrameBytes = bytes;
}
}
NSUInteger max = 0;
if (self.maxBufferSize > 0) {
@ -410,7 +344,7 @@
maxBufferCount = 1;
}
self.maxBufferCount = maxBufferCount;
self.framePool.maxBufferCount = maxBufferCount;
}
+ (NSString *)defaultRunLoopMode {

View File

@ -0,0 +1,40 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
#import "SDImageCoder.h"
NS_ASSUME_NONNULL_BEGIN
/// A per-provider (provider means, AnimatedImage object) based frame pool, each player who use the same provider share the same frame buffer
@interface SDImageFramePool : NSObject
/// Register and return back a frame pool, also increase reference count
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider;
/// Unregister a frame pool, also decrease reference count, if zero dealloc the frame pool
+ (void)unregisterProvider:(id<SDAnimatedImageProvider>)provider;
/// Prefetch the current frame, query using `frameAtIndex:` by caller to check whether finished.
- (void)prefetchFrameAtIndex:(NSUInteger)index;
/// Control the max buffer count for current frame pool, used for RAM/CPU balance, default unlimited
@property (nonatomic, assign) NSUInteger maxBufferCount;
/// Control the max concurrent fetch queue operation count, used for CPU balance, default 1
@property (nonatomic, assign) NSUInteger maxConcurrentCount;
// Frame Operations
@property (nonatomic, readonly) NSUInteger currentFrameCount;
- (nullable UIImage *)frameAtIndex:(NSUInteger)index;
- (void)setFrame:(nullable UIImage *)frame atIndex:(NSUInteger)index;
- (void)removeFrameAtIndex:(NSUInteger)index;
- (void)removeAllFrames;
NS_ASSUME_NONNULL_END
@end

View File

@ -0,0 +1,164 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDImageFramePool.h"
#import "SDInternalMacros.h"
#import "objc/runtime.h"
@interface SDImageFramePool ()
@property (class, readonly) NSMapTable *providerFramePoolMap;
@property (weak) id<SDAnimatedImageProvider> provider;
@property (atomic) NSUInteger registerCount;
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@end
// Lock to ensure atomic behavior
SD_LOCK_DECLARE_STATIC(_providerFramePoolMapLock);
@implementation SDImageFramePool
+ (NSMapTable *)providerFramePoolMap {
static NSMapTable *providerFramePoolMap;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
});
return providerFramePoolMap;
}
#pragma mark - Life Cycle
- (instancetype)init {
self = [super init];
if (self) {
_frameBuffer = [NSMutableDictionary dictionary];
_fetchQueue = [[NSOperationQueue alloc] init];
_fetchQueue.maxConcurrentOperationCount = 1;
_fetchQueue.name = @"com.hackemist.SDImageFramePool.fetchQueue";
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[self removeAllFrames];
}
+ (void)initialize {
// Lock to ensure atomic behavior
SD_LOCK_INIT(_providerFramePoolMapLock);
}
+ (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider {
// Lock to ensure atomic behavior
SD_LOCK(_providerFramePoolMapLock);
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
if (!framePool) {
framePool = [[SDImageFramePool alloc] init];
framePool.provider = provider;
[self.providerFramePoolMap setObject:framePool forKey:provider];
}
framePool.registerCount += 1;
SD_UNLOCK(_providerFramePoolMapLock);
return framePool;
}
+ (void)unregisterProvider:(id<SDAnimatedImageProvider>)provider {
// Lock to ensure atomic behavior
SD_LOCK(_providerFramePoolMapLock);
SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
if (!framePool) {
SD_UNLOCK(_providerFramePoolMapLock);
return;
}
framePool.registerCount -= 1;
if (framePool.registerCount == 0) {
[self.providerFramePoolMap removeObjectForKey:provider];
}
SD_UNLOCK(_providerFramePoolMapLock);
}
- (void)prefetchFrameAtIndex:(NSUInteger)index {
@synchronized (self) {
NSUInteger frameCount = self.frameBuffer.count;
if (frameCount > self.maxBufferCount) {
// Remove the frame buffer if need
// TODO, use LRU or better algorithm to detect which frames to clear
self.frameBuffer[@(index - 1)] = nil;
self.frameBuffer[@(index + 1)] = nil;
}
}
if (self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
id<SDAnimatedImageProvider> animatedProvider = self.provider;
@weakify(self);
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
@strongify(self);
if (!self) {
return;
}
UIImage *frame = [animatedProvider animatedImageFrameAtIndex:index];
[self setFrame:frame atIndex:index];
}];
[self.fetchQueue addOperation:operation];
}
}
- (void)setMaxConcurrentCount:(NSUInteger)maxConcurrentCount {
self.fetchQueue.maxConcurrentOperationCount = maxConcurrentCount;
}
- (NSUInteger)currentFrameCount {
NSUInteger frameCount = 0;
@synchronized (self) {
frameCount = self.frameBuffer.count;
}
return frameCount;
}
- (void)setFrame:(UIImage *)frame atIndex:(NSUInteger)index {
@synchronized (self) {
self.frameBuffer[@(index)] = frame;
}
}
- (UIImage *)frameAtIndex:(NSUInteger)index {
UIImage *frame;
@synchronized (self) {
frame = self.frameBuffer[@(index)];
}
return frame;
}
- (void)removeFrameAtIndex:(NSUInteger)index {
@synchronized (self) {
self.frameBuffer[@(index)] = nil;
}
}
- (void)removeAllFrames {
@synchronized (self) {
[self.frameBuffer removeAllObjects];
}
}
@end

View File

@ -9,6 +9,7 @@
#import "SDTestCase.h"
#import "SDInternalMacros.h"
#import "SDImageFramePool.h"
#import <KVOController/KVOController.h>
#import <SDWebImageWebPCoder/SDWebImageWebPCoder.h>
@ -59,7 +60,7 @@ static BOOL _isCalled;
@interface SDAnimatedImagePlayer ()
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
@property (nonatomic, strong) SDImageFramePool *framePool;
@end
@ -382,7 +383,7 @@ static BOOL _isCalled;
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.player.frameBuffer.count).beGreaterThan(0);
expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
expect(imageView.currentFrameIndex).beGreaterThan(0);
});
@ -392,7 +393,7 @@ static BOOL _isCalled;
#else
imageView.animates = NO;
#endif
expect(imageView.player.frameBuffer.count).beGreaterThan(0);
expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
expect(imageView.currentFrameIndex).beGreaterThan(0);
[imageView removeFromSuperview];
@ -420,7 +421,7 @@ static BOOL _isCalled;
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.player.frameBuffer.count).beGreaterThan(0);
expect(imageView.player.framePool.currentFrameCount).beGreaterThan(0);
expect(imageView.currentFrameIndex).beGreaterThan(0);
});
@ -430,7 +431,7 @@ static BOOL _isCalled;
#else
imageView.animates = NO;
#endif
expect(imageView.player.frameBuffer.count).equal(0);
expect(imageView.player.framePool.currentFrameCount).equal(0);
[imageView removeFromSuperview];
[expectation fulfill];