123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769 |
- /*
- * 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 "SDAnimatedImageView.h"
- #if SD_UIKIT || SD_MAC
- #import "UIImage+Metadata.h"
- #import "NSImage+Compatibility.h"
- #import "SDWeakProxy.h"
- #import "SDInternalMacros.h"
- #import <mach/mach.h>
- #import <objc/runtime.h>
- #if SD_MAC
- #import <CoreVideo/CoreVideo.h>
- static CVReturn renderCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
- #endif
- static NSUInteger SDDeviceTotalMemory() {
- return (NSUInteger)[[NSProcessInfo processInfo] physicalMemory];
- }
- static NSUInteger SDDeviceFreeMemory() {
- mach_port_t host_port = mach_host_self();
- mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
- vm_size_t page_size;
- vm_statistics_data_t vm_stat;
- kern_return_t kern;
-
- kern = host_page_size(host_port, &page_size);
- if (kern != KERN_SUCCESS) return 0;
- kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
- if (kern != KERN_SUCCESS) return 0;
- return vm_stat.free_count * page_size;
- }
- @interface SDAnimatedImageView () <CALayerDelegate> {
- NSRunLoopMode _runLoopMode;
- BOOL _initFinished; // Extra flag to mark the `commonInit` is called
- }
- @property (nonatomic, strong, readwrite) UIImage *currentFrame;
- @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
- @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
- @property (nonatomic, assign) NSUInteger totalFrameCount;
- @property (nonatomic, assign) NSUInteger totalLoopCount;
- @property (nonatomic, strong) UIImage<SDAnimatedImage> *animatedImage;
- @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
- @property (nonatomic, assign) NSTimeInterval currentTime;
- @property (nonatomic, assign) BOOL bufferMiss;
- @property (nonatomic, assign) BOOL shouldAnimate;
- @property (nonatomic, assign) BOOL isProgressive;
- @property (nonatomic, assign) NSUInteger maxBufferCount;
- @property (nonatomic, strong) NSOperationQueue *fetchQueue;
- @property (nonatomic, strong) dispatch_semaphore_t lock;
- @property (nonatomic, assign) CGFloat animatedImageScale;
- #if SD_MAC
- @property (nonatomic, assign) CVDisplayLinkRef displayLink;
- #else
- @property (nonatomic, strong) CADisplayLink *displayLink;
- #endif
- @end
- @implementation SDAnimatedImageView
- #if SD_UIKIT
- @dynamic animationRepeatCount; // we re-use this property from `UIImageView` super class on iOS.
- #endif
- #pragma mark - Initializers
- #if SD_MAC
- + (instancetype)imageViewWithImage:(NSImage *)image
- {
- NSRect frame = NSMakeRect(0, 0, image.size.width, image.size.height);
- SDAnimatedImageView *imageView = [[SDAnimatedImageView alloc] initWithFrame:frame];
- [imageView setImage:image];
- return imageView;
- }
- #else
- // -initWithImage: isn't documented as a designated initializer of UIImageView, but it actually seems to be.
- // Using -initWithImage: doesn't call any of the other designated initializers.
- - (instancetype)initWithImage:(UIImage *)image
- {
- self = [super initWithImage:image];
- if (self) {
- [self commonInit];
- }
- return self;
- }
- // -initWithImage:highlightedImage: also isn't documented as a designated initializer of UIImageView, but it doesn't call any other designated initializers.
- - (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage
- {
- self = [super initWithImage:image highlightedImage:highlightedImage];
- if (self) {
- [self commonInit];
- }
- return self;
- }
- #endif
- - (instancetype)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self) {
- [self commonInit];
- }
- return self;
- }
- - (instancetype)initWithCoder:(NSCoder *)aDecoder
- {
- self = [super initWithCoder:aDecoder];
- if (self) {
- [self commonInit];
- }
- return self;
- }
- - (void)commonInit
- {
- // Pay attention that UIKit's `initWithImage:` will trigger a `setImage:` during initialization before this `commonInit`.
- // So the properties which rely on this order, should using lazy-evaluation or do extra check in `setImage:`.
- self.shouldCustomLoopCount = NO;
- self.shouldIncrementalLoad = YES;
- #if SD_MAC
- self.wantsLayer = YES;
- // Default value from `NSImageView`
- self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
- self.imageScaling = NSImageScaleProportionallyDown;
- self.imageAlignment = NSImageAlignCenter;
- #endif
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- // Mark commonInit finished
- _initFinished = YES;
- }
- - (void)resetAnimatedImage
- {
- self.animatedImage = nil;
- self.totalFrameCount = 0;
- self.totalLoopCount = 0;
- self.currentFrame = nil;
- self.currentFrameIndex = 0;
- self.currentLoopCount = 0;
- self.currentTime = 0;
- self.bufferMiss = NO;
- 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);
- }
- - (void)resetProgressiveImage
- {
- self.animatedImage = nil;
- self.totalFrameCount = 0;
- self.totalLoopCount = 0;
- // preserve current state
- self.shouldAnimate = NO;
- self.isProgressive = YES;
- self.maxBufferCount = 0;
- self.animatedImageScale = 1;
- // preserve buffer cache
- }
- #pragma mark - Accessors
- #pragma mark Public
- - (void)setImage:(UIImage *)image
- {
- if (self.image == image) {
- return;
- }
-
- // Check Progressive rendering
- [self updateIsProgressiveWithImage:image];
-
- if (self.isProgressive) {
- // Reset all value, but keep current state
- [self resetProgressiveImage];
- } else {
- // Stop animating
- [self stopAnimating];
- // Reset all value
- [self resetAnimatedImage];
- }
-
- // We need call super method to keep function. This will impliedly call `setNeedsDisplay`. But we have no way to avoid this when using animated image. So we call `setNeedsDisplay` again at the end.
- super.image = image;
- if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) {
- NSUInteger animatedImageFrameCount = ((UIImage<SDAnimatedImage> *)image).animatedImageFrameCount;
- // Check the frame count
- if (animatedImageFrameCount <= 1) {
- return;
- }
- // If progressive rendering is disabled but animated image is incremental. Only show poster image
- if (!self.isProgressive && image.sd_isIncremental) {
- return;
- }
- self.animatedImage = (UIImage<SDAnimatedImage> *)image;
- self.totalFrameCount = animatedImageFrameCount;
- // Get the current frame and loop count.
- self.totalLoopCount = self.animatedImage.animatedImageLoopCount;
- // Get the scale
- self.animatedImageScale = image.scale;
- if (!self.isProgressive) {
- self.currentFrame = image;
- SD_LOCK(self.lock);
- self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
- SD_UNLOCK(self.lock);
- }
-
- // Ensure disabled highlighting; it's not supported (see `-setHighlighted:`).
- super.highlighted = NO;
-
- // Calculate max buffer size
- [self calculateMaxBufferCount];
- // Update should animate
- [self updateShouldAnimate];
- if (self.shouldAnimate) {
- [self startAnimating];
- }
-
- [self.layer setNeedsDisplay];
- #if SD_MAC
- [self.layer displayIfNeeded]; // macOS's imageViewLayer may not equal to self.layer. But `[super setImage:]` will impliedly mark it needsDisplay. We call `[self.layer displayIfNeeded]` to immediately refresh the imageViewLayer to avoid flashing
- #endif
- }
- }
- #if SD_UIKIT
- - (void)setRunLoopMode:(NSRunLoopMode)runLoopMode
- {
- if ([_runLoopMode isEqual:runLoopMode]) {
- return;
- }
- if (_displayLink) {
- if (_runLoopMode) {
- [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runLoopMode];
- }
- if (runLoopMode.length > 0) {
- [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
- }
- }
- _runLoopMode = [runLoopMode copy];
- }
- - (NSRunLoopMode)runLoopMode
- {
- if (!_runLoopMode) {
- _runLoopMode = [[self class] defaultRunLoopMode];
- }
- return _runLoopMode;
- }
- #endif
- - (BOOL)shouldIncrementalLoad {
- if (!_initFinished) {
- return YES; // Defaults to YES
- }
- return _initFinished;
- }
- #pragma mark - Private
- - (NSOperationQueue *)fetchQueue
- {
- if (!_fetchQueue) {
- _fetchQueue = [[NSOperationQueue alloc] init];
- _fetchQueue.maxConcurrentOperationCount = 1;
- }
- return _fetchQueue;
- }
- - (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer
- {
- if (!_frameBuffer) {
- _frameBuffer = [NSMutableDictionary dictionary];
- }
- return _frameBuffer;
- }
- - (dispatch_semaphore_t)lock {
- if (!_lock) {
- _lock = dispatch_semaphore_create(1);
- }
- return _lock;
- }
- #if SD_MAC
- - (CVDisplayLinkRef)displayLink
- {
- if (!_displayLink) {
- CVReturn error = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
- if (error) {
- return NULL;
- }
- CVDisplayLinkSetOutputCallback(_displayLink, renderCallback, (__bridge void *)self);
- }
- return _displayLink;
- }
- #else
- - (CADisplayLink *)displayLink
- {
- if (!_displayLink) {
- // It is important to note the use of a weak proxy here to avoid a retain cycle. `-displayLinkWithTarget:selector:`
- // will retain its target until it is invalidated. We use a weak proxy so that the image view will get deallocated
- // independent of the display link's lifetime. Upon image view deallocation, we invalidate the display
- // link which will lead to the deallocation of both the display link and the weak proxy.
- SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self];
- _displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
- [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
- }
- return _displayLink;
- }
- #endif
- #pragma mark - Life Cycle
- - (void)dealloc
- {
- // Removes the display link from all run loop modes.
- #if SD_MAC
- if (_displayLink) {
- CVDisplayLinkRelease(_displayLink);
- _displayLink = NULL;
- }
- #else
- [_displayLink invalidate];
- _displayLink = nil;
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- }
- - (void)didReceiveMemoryWarning:(NSNotification *)notification {
- [_fetchQueue cancelAllOperations];
- [_fetchQueue addOperationWithBlock:^{
- 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);
- }];
- }
- #pragma mark - UIView Method Overrides
- #pragma mark Observing View-Related Changes
- #if SD_MAC
- - (void)viewDidMoveToSuperview
- #else
- - (void)didMoveToSuperview
- #endif
- {
- #if SD_MAC
- [super viewDidMoveToSuperview];
- #else
- [super didMoveToSuperview];
- #endif
-
- [self updateShouldAnimate];
- if (self.shouldAnimate) {
- [self startAnimating];
- } else {
- [self stopAnimating];
- }
- }
- #if SD_MAC
- - (void)viewDidMoveToWindow
- #else
- - (void)didMoveToWindow
- #endif
- {
- #if SD_MAC
- [super viewDidMoveToWindow];
- #else
- [super didMoveToWindow];
- #endif
-
- [self updateShouldAnimate];
- if (self.shouldAnimate) {
- [self startAnimating];
- } else {
- [self stopAnimating];
- }
- }
- #if SD_MAC
- - (void)setAlphaValue:(CGFloat)alphaValue
- #else
- - (void)setAlpha:(CGFloat)alpha
- #endif
- {
- #if SD_MAC
- [super setAlphaValue:alphaValue];
- #else
- [super setAlpha:alpha];
- #endif
-
- [self updateShouldAnimate];
- if (self.shouldAnimate) {
- [self startAnimating];
- } else {
- [self stopAnimating];
- }
- }
- - (void)setHidden:(BOOL)hidden
- {
- [super setHidden:hidden];
-
- [self updateShouldAnimate];
- if (self.shouldAnimate) {
- [self startAnimating];
- } else {
- [self stopAnimating];
- }
- }
- #pragma mark - UIImageView Method Overrides
- #pragma mark Image Data
- - (void)startAnimating
- {
- if (self.animatedImage) {
- #if SD_MAC
- CVDisplayLinkStart(self.displayLink);
- #else
- self.displayLink.paused = NO;
- #endif
- } else {
- #if SD_UIKIT
- [super startAnimating];
- #endif
- }
- }
- - (void)stopAnimating
- {
- if (self.animatedImage) {
- #if SD_MAC
- CVDisplayLinkStop(_displayLink);
- #else
- _displayLink.paused = YES;
- #endif
- } else {
- #if SD_UIKIT
- [super stopAnimating];
- #endif
- }
- }
- - (BOOL)isAnimating
- {
- BOOL isAnimating = NO;
- if (self.animatedImage) {
- #if SD_MAC
- isAnimating = CVDisplayLinkIsRunning(self.displayLink);
- #else
- isAnimating = !self.displayLink.isPaused;
- #endif
- } else {
- #if SD_UIKIT
- isAnimating = [super isAnimating];
- #endif
- }
- return isAnimating;
- }
- #if SD_MAC
- - (void)setAnimates:(BOOL)animates
- {
- [super setAnimates:animates];
- if (animates) {
- [self startAnimating];
- } else {
- [self stopAnimating];
- }
- }
- #endif
- #pragma mark Highlighted Image Unsupport
- - (void)setHighlighted:(BOOL)highlighted
- {
- // Highlighted image is unsupported for animated images, but implementing it breaks the image view when embedded in a UICollectionViewCell.
- if (!self.animatedImage) {
- [super setHighlighted:highlighted];
- }
- }
- #pragma mark - Private Methods
- #pragma mark Animation
- // Don't repeatedly check our window & superview in `-displayDidRefresh:` for performance reasons.
- // Just update our cached value whenever the animated image or visibility (window, superview, hidden, alpha) is changed.
- - (void)updateShouldAnimate
- {
- #if SD_MAC
- BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alphaValue > 0.0 && self.animates;
- #else
- BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alpha > 0.0;
- #endif
- self.shouldAnimate = self.animatedImage && self.totalFrameCount > 1 && isVisible;
- }
- // Update progressive status only after `setImage:` call.
- - (void)updateIsProgressiveWithImage:(UIImage *)image
- {
- self.isProgressive = NO;
- if (!self.shouldIncrementalLoad) {
- // Early return
- return;
- }
- if ([image conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental) {
- UIImage *previousImage = self.image;
- if ([previousImage conformsToProtocol:@protocol(SDAnimatedImage)] && previousImage.sd_isIncremental) {
- NSData *previousData = [((UIImage<SDAnimatedImage> *)previousImage) animatedImageData];
- NSData *currentData = [((UIImage<SDAnimatedImage> *)image) animatedImageData];
- // Check whether to use progressive rendering or not
- if (!previousData || !currentData) {
- // Early return
- return;
- }
-
- // Warning: normally the `previousData` is same instance as `currentData` because our `SDAnimatedImage` class share the same `coder` instance internally. But there may be a race condition, that later retrived `currentData` is already been updated and it's not the same instance as `previousData`.
- // And for protocol extensible design, we should not assume `SDAnimatedImage` protocol implementations always share same instance. So both of two reasons, we need that `rangeOfData` check.
- if ([currentData isEqualToData:previousData]) {
- // If current data is the same data (or instance) as previous data
- self.isProgressive = YES;
- } else if (currentData.length > previousData.length) {
- // If current data is appended by previous data, use `NSDataSearchAnchored`, search is limited to start of currentData
- NSRange range = [currentData rangeOfData:previousData options:NSDataSearchAnchored range:NSMakeRange(0, previousData.length)];
- if (range.location != NSNotFound) {
- // Contains hole previous data and they start with the same beginning
- self.isProgressive = YES;
- }
- }
- } else {
- // Previous image is not progressive, so start progressive rendering
- self.isProgressive = YES;
- }
- }
- }
- #if SD_MAC
- - (void)displayDidRefresh:(CVDisplayLinkRef)displayLink duration:(NSTimeInterval)duration
- #else
- - (void)displayDidRefresh:(CADisplayLink *)displayLink
- #endif
- {
- // If for some reason a wild call makes it through when we shouldn't be animating, bail.
- // Early return!
- if (!self.shouldAnimate) {
- return;
- }
-
- #if SD_UIKIT
- NSTimeInterval duration = displayLink.duration * displayLink.frameInterval;
- #endif
- NSUInteger totalFrameCount = self.totalFrameCount;
- NSUInteger currentFrameIndex = self.currentFrameIndex;
- NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
-
- // Check if we have the frame buffer firstly to improve performance
- if (!self.bufferMiss) {
- // Then check if timestamp is reached
- self.currentTime += duration;
- NSTimeInterval currentDuration = [self.animatedImage animatedImageDurationAtIndex:currentFrameIndex];
- if (self.currentTime < currentDuration) {
- // Current frame timestamp not reached, return
- return;
- }
- self.currentTime -= currentDuration;
- NSTimeInterval nextDuration = [self.animatedImage animatedImageDurationAtIndex:nextFrameIndex];
- if (self.currentTime > nextDuration) {
- // Do not skip frame
- self.currentTime = nextDuration;
- }
- }
-
- // Update the current frame
- UIImage *currentFrame;
- UIImage *fetchFrame;
- SD_LOCK(self.lock);
- currentFrame = self.frameBuffer[@(currentFrameIndex)];
- fetchFrame = currentFrame ? self.frameBuffer[@(nextFrameIndex)] : nil;
- SD_UNLOCK(self.lock);
- BOOL bufferFull = NO;
- if (currentFrame) {
- SD_LOCK(self.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(self.lock);
- self.currentFrame = currentFrame;
- self.currentFrameIndex = nextFrameIndex;
- self.bufferMiss = NO;
- [self.layer setNeedsDisplay];
- } else {
- self.bufferMiss = YES;
- }
-
- // Update the loop count when last frame rendered
- if (nextFrameIndex == 0 && !self.bufferMiss) {
- // Progressive image reach the current last frame index. Keep the state and stop animating. Wait for later restart
- if (self.isProgressive) {
- // Recovery the current frame index and removed frame buffer (See above)
- self.currentFrameIndex = currentFrameIndex;
- SD_LOCK(self.lock);
- self.frameBuffer[@(currentFrameIndex)] = self.currentFrame;
- SD_UNLOCK(self.lock);
- [self stopAnimating];
- return;
- }
- // Update the loop count
- self.currentLoopCount++;
- // if reached the max loop count, stop animating, 0 means loop indefinitely
- NSUInteger maxLoopCount = self.shouldCustomLoopCount ? self.animationRepeatCount : self.totalLoopCount;
- if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
- [self stopAnimating];
- return;
- }
- }
-
- // Check if we should prefetch next frame or current frame
- NSUInteger fetchFrameIndex;
- if (self.bufferMiss) {
- // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
- fetchFrameIndex = currentFrameIndex;
- } else {
- // Or, most cases, the decode speed is faster than render speed, we fetch next frame
- fetchFrameIndex = nextFrameIndex;
- }
-
- if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
- // Prefetch next frame in background queue
- UIImage<SDAnimatedImage> *animatedImage = self.animatedImage;
- NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
- UIImage *frame = [animatedImage animatedImageFrameAtIndex:fetchFrameIndex];
- SD_LOCK(self.lock);
- self.frameBuffer[@(fetchFrameIndex)] = frame;
- SD_UNLOCK(self.lock);
- }];
- [self.fetchQueue addOperation:operation];
- }
- }
- + (NSString *)defaultRunLoopMode
- {
- // Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations.
- return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
- }
- #pragma mark Providing the Layer's Content
- #pragma mark - CALayerDelegate
- - (void)displayLayer:(CALayer *)layer
- {
- if (_currentFrame) {
- layer.contentsScale = self.animatedImageScale;
- layer.contents = (__bridge id)_currentFrame.CGImage;
- }
- }
- #if SD_MAC
- // Layer-backed NSImageView optionally optimize to use a subview to do actual layer rendering.
- // When the optimization is turned on, it calls `updateLayer` instead of `displayLayer:` to update subview's layer.
- // When the optimization it turned off, this return nil and calls `displayLayer:` directly.
- - (CALayer *)imageViewLayer {
- NSView *imageView = imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageView"));
- if (!imageView) {
- // macOS 10.14
- imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageSubview"));
- }
- return imageView.layer;
- }
- - (void)updateLayer
- {
- if (_currentFrame) {
- [self displayLayer:self.imageViewLayer];
- } else {
- [super updateLayer];
- }
- }
- - (BOOL)wantsUpdateLayer {
- // AppKit is different from UIKit, it need extra check before the layer is updated
- // When we use the custom animation, the layer.setNeedsDisplay is directly called from display link (See `displayDidRefresh:`). However, for normal image rendering, we must implements and return YES to mark it need display
- if (_currentFrame) {
- return NO;
- } else {
- return YES;
- }
- }
- #endif
- #pragma mark - Util
- - (void)calculateMaxBufferCount {
- NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
- if (bytes == 0) bytes = 1024;
-
- NSUInteger max = 0;
- if (self.maxBufferSize > 0) {
- max = self.maxBufferSize;
- } else {
- // Calculate based on current memory, these factors are by experience
- NSUInteger total = SDDeviceTotalMemory();
- NSUInteger free = SDDeviceFreeMemory();
- max = MIN(total * 0.2, free * 0.6);
- }
-
- NSUInteger maxBufferCount = (double)max / (double)bytes;
- if (!maxBufferCount) {
- // At least 1 frame
- maxBufferCount = 1;
- }
-
- self.maxBufferCount = maxBufferCount;
- }
- @end
- #if SD_MAC
- static CVReturn renderCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
- // Calculate refresh duration
- NSTimeInterval duration = (double)inOutputTime->videoRefreshPeriod / ((double)inOutputTime->videoTimeScale * inOutputTime->rateScalar);
- // CVDisplayLink callback is not on main queue
- SDAnimatedImageView *imageView = (__bridge SDAnimatedImageView *)displayLinkContext;
- __weak SDAnimatedImageView *weakImageView = imageView;
- dispatch_async(dispatch_get_main_queue(), ^{
- [weakImageView displayDidRefresh:displayLink duration:duration];
- });
- return kCVReturnSuccess;
- }
- #endif
- #endif
|