SDAnimatedImageView.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "SDAnimatedImageView.h"
  9. #if SD_UIKIT || SD_MAC
  10. #import "UIImage+Metadata.h"
  11. #import "NSImage+Compatibility.h"
  12. #import "SDWeakProxy.h"
  13. #import "SDInternalMacros.h"
  14. #import <mach/mach.h>
  15. #import <objc/runtime.h>
  16. #if SD_MAC
  17. #import <CoreVideo/CoreVideo.h>
  18. static CVReturn renderCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
  19. #endif
  20. static NSUInteger SDDeviceTotalMemory() {
  21. return (NSUInteger)[[NSProcessInfo processInfo] physicalMemory];
  22. }
  23. static NSUInteger SDDeviceFreeMemory() {
  24. mach_port_t host_port = mach_host_self();
  25. mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
  26. vm_size_t page_size;
  27. vm_statistics_data_t vm_stat;
  28. kern_return_t kern;
  29. kern = host_page_size(host_port, &page_size);
  30. if (kern != KERN_SUCCESS) return 0;
  31. kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
  32. if (kern != KERN_SUCCESS) return 0;
  33. return vm_stat.free_count * page_size;
  34. }
  35. @interface SDAnimatedImageView () <CALayerDelegate> {
  36. NSRunLoopMode _runLoopMode;
  37. BOOL _initFinished; // Extra flag to mark the `commonInit` is called
  38. }
  39. @property (nonatomic, strong, readwrite) UIImage *currentFrame;
  40. @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
  41. @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
  42. @property (nonatomic, assign) NSUInteger totalFrameCount;
  43. @property (nonatomic, assign) NSUInteger totalLoopCount;
  44. @property (nonatomic, strong) UIImage<SDAnimatedImage> *animatedImage;
  45. @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
  46. @property (nonatomic, assign) NSTimeInterval currentTime;
  47. @property (nonatomic, assign) BOOL bufferMiss;
  48. @property (nonatomic, assign) BOOL shouldAnimate;
  49. @property (nonatomic, assign) BOOL isProgressive;
  50. @property (nonatomic, assign) NSUInteger maxBufferCount;
  51. @property (nonatomic, strong) NSOperationQueue *fetchQueue;
  52. @property (nonatomic, strong) dispatch_semaphore_t lock;
  53. @property (nonatomic, assign) CGFloat animatedImageScale;
  54. #if SD_MAC
  55. @property (nonatomic, assign) CVDisplayLinkRef displayLink;
  56. #else
  57. @property (nonatomic, strong) CADisplayLink *displayLink;
  58. #endif
  59. @end
  60. @implementation SDAnimatedImageView
  61. #if SD_UIKIT
  62. @dynamic animationRepeatCount; // we re-use this property from `UIImageView` super class on iOS.
  63. #endif
  64. #pragma mark - Initializers
  65. #if SD_MAC
  66. + (instancetype)imageViewWithImage:(NSImage *)image
  67. {
  68. NSRect frame = NSMakeRect(0, 0, image.size.width, image.size.height);
  69. SDAnimatedImageView *imageView = [[SDAnimatedImageView alloc] initWithFrame:frame];
  70. [imageView setImage:image];
  71. return imageView;
  72. }
  73. #else
  74. // -initWithImage: isn't documented as a designated initializer of UIImageView, but it actually seems to be.
  75. // Using -initWithImage: doesn't call any of the other designated initializers.
  76. - (instancetype)initWithImage:(UIImage *)image
  77. {
  78. self = [super initWithImage:image];
  79. if (self) {
  80. [self commonInit];
  81. }
  82. return self;
  83. }
  84. // -initWithImage:highlightedImage: also isn't documented as a designated initializer of UIImageView, but it doesn't call any other designated initializers.
  85. - (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage
  86. {
  87. self = [super initWithImage:image highlightedImage:highlightedImage];
  88. if (self) {
  89. [self commonInit];
  90. }
  91. return self;
  92. }
  93. #endif
  94. - (instancetype)initWithFrame:(CGRect)frame
  95. {
  96. self = [super initWithFrame:frame];
  97. if (self) {
  98. [self commonInit];
  99. }
  100. return self;
  101. }
  102. - (instancetype)initWithCoder:(NSCoder *)aDecoder
  103. {
  104. self = [super initWithCoder:aDecoder];
  105. if (self) {
  106. [self commonInit];
  107. }
  108. return self;
  109. }
  110. - (void)commonInit
  111. {
  112. // Pay attention that UIKit's `initWithImage:` will trigger a `setImage:` during initialization before this `commonInit`.
  113. // So the properties which rely on this order, should using lazy-evaluation or do extra check in `setImage:`.
  114. self.shouldCustomLoopCount = NO;
  115. self.shouldIncrementalLoad = YES;
  116. #if SD_MAC
  117. self.wantsLayer = YES;
  118. // Default value from `NSImageView`
  119. self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
  120. self.imageScaling = NSImageScaleProportionallyDown;
  121. self.imageAlignment = NSImageAlignCenter;
  122. #endif
  123. #if SD_UIKIT
  124. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  125. #endif
  126. // Mark commonInit finished
  127. _initFinished = YES;
  128. }
  129. - (void)resetAnimatedImage
  130. {
  131. self.animatedImage = nil;
  132. self.totalFrameCount = 0;
  133. self.totalLoopCount = 0;
  134. self.currentFrame = nil;
  135. self.currentFrameIndex = 0;
  136. self.currentLoopCount = 0;
  137. self.currentTime = 0;
  138. self.bufferMiss = NO;
  139. self.shouldAnimate = NO;
  140. self.isProgressive = NO;
  141. self.maxBufferCount = 0;
  142. self.animatedImageScale = 1;
  143. [_fetchQueue cancelAllOperations];
  144. _fetchQueue = nil;
  145. SD_LOCK(self.lock);
  146. [_frameBuffer removeAllObjects];
  147. _frameBuffer = nil;
  148. SD_UNLOCK(self.lock);
  149. }
  150. - (void)resetProgressiveImage
  151. {
  152. self.animatedImage = nil;
  153. self.totalFrameCount = 0;
  154. self.totalLoopCount = 0;
  155. // preserve current state
  156. self.shouldAnimate = NO;
  157. self.isProgressive = YES;
  158. self.maxBufferCount = 0;
  159. self.animatedImageScale = 1;
  160. // preserve buffer cache
  161. }
  162. #pragma mark - Accessors
  163. #pragma mark Public
  164. - (void)setImage:(UIImage *)image
  165. {
  166. if (self.image == image) {
  167. return;
  168. }
  169. // Check Progressive rendering
  170. [self updateIsProgressiveWithImage:image];
  171. if (self.isProgressive) {
  172. // Reset all value, but keep current state
  173. [self resetProgressiveImage];
  174. } else {
  175. // Stop animating
  176. [self stopAnimating];
  177. // Reset all value
  178. [self resetAnimatedImage];
  179. }
  180. // 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.
  181. super.image = image;
  182. if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) {
  183. NSUInteger animatedImageFrameCount = ((UIImage<SDAnimatedImage> *)image).animatedImageFrameCount;
  184. // Check the frame count
  185. if (animatedImageFrameCount <= 1) {
  186. return;
  187. }
  188. // If progressive rendering is disabled but animated image is incremental. Only show poster image
  189. if (!self.isProgressive && image.sd_isIncremental) {
  190. return;
  191. }
  192. self.animatedImage = (UIImage<SDAnimatedImage> *)image;
  193. self.totalFrameCount = animatedImageFrameCount;
  194. // Get the current frame and loop count.
  195. self.totalLoopCount = self.animatedImage.animatedImageLoopCount;
  196. // Get the scale
  197. self.animatedImageScale = image.scale;
  198. if (!self.isProgressive) {
  199. self.currentFrame = image;
  200. SD_LOCK(self.lock);
  201. self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
  202. SD_UNLOCK(self.lock);
  203. }
  204. // Ensure disabled highlighting; it's not supported (see `-setHighlighted:`).
  205. super.highlighted = NO;
  206. // Calculate max buffer size
  207. [self calculateMaxBufferCount];
  208. // Update should animate
  209. [self updateShouldAnimate];
  210. if (self.shouldAnimate) {
  211. [self startAnimating];
  212. }
  213. [self.layer setNeedsDisplay];
  214. #if SD_MAC
  215. [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
  216. #endif
  217. }
  218. }
  219. #if SD_UIKIT
  220. - (void)setRunLoopMode:(NSRunLoopMode)runLoopMode
  221. {
  222. if ([_runLoopMode isEqual:runLoopMode]) {
  223. return;
  224. }
  225. if (_displayLink) {
  226. if (_runLoopMode) {
  227. [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runLoopMode];
  228. }
  229. if (runLoopMode.length > 0) {
  230. [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
  231. }
  232. }
  233. _runLoopMode = [runLoopMode copy];
  234. }
  235. - (NSRunLoopMode)runLoopMode
  236. {
  237. if (!_runLoopMode) {
  238. _runLoopMode = [[self class] defaultRunLoopMode];
  239. }
  240. return _runLoopMode;
  241. }
  242. #endif
  243. - (BOOL)shouldIncrementalLoad {
  244. if (!_initFinished) {
  245. return YES; // Defaults to YES
  246. }
  247. return _initFinished;
  248. }
  249. #pragma mark - Private
  250. - (NSOperationQueue *)fetchQueue
  251. {
  252. if (!_fetchQueue) {
  253. _fetchQueue = [[NSOperationQueue alloc] init];
  254. _fetchQueue.maxConcurrentOperationCount = 1;
  255. }
  256. return _fetchQueue;
  257. }
  258. - (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer
  259. {
  260. if (!_frameBuffer) {
  261. _frameBuffer = [NSMutableDictionary dictionary];
  262. }
  263. return _frameBuffer;
  264. }
  265. - (dispatch_semaphore_t)lock {
  266. if (!_lock) {
  267. _lock = dispatch_semaphore_create(1);
  268. }
  269. return _lock;
  270. }
  271. #if SD_MAC
  272. - (CVDisplayLinkRef)displayLink
  273. {
  274. if (!_displayLink) {
  275. CVReturn error = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
  276. if (error) {
  277. return NULL;
  278. }
  279. CVDisplayLinkSetOutputCallback(_displayLink, renderCallback, (__bridge void *)self);
  280. }
  281. return _displayLink;
  282. }
  283. #else
  284. - (CADisplayLink *)displayLink
  285. {
  286. if (!_displayLink) {
  287. // It is important to note the use of a weak proxy here to avoid a retain cycle. `-displayLinkWithTarget:selector:`
  288. // will retain its target until it is invalidated. We use a weak proxy so that the image view will get deallocated
  289. // independent of the display link's lifetime. Upon image view deallocation, we invalidate the display
  290. // link which will lead to the deallocation of both the display link and the weak proxy.
  291. SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self];
  292. _displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
  293. [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
  294. }
  295. return _displayLink;
  296. }
  297. #endif
  298. #pragma mark - Life Cycle
  299. - (void)dealloc
  300. {
  301. // Removes the display link from all run loop modes.
  302. #if SD_MAC
  303. if (_displayLink) {
  304. CVDisplayLinkRelease(_displayLink);
  305. _displayLink = NULL;
  306. }
  307. #else
  308. [_displayLink invalidate];
  309. _displayLink = nil;
  310. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  311. #endif
  312. }
  313. - (void)didReceiveMemoryWarning:(NSNotification *)notification {
  314. [_fetchQueue cancelAllOperations];
  315. [_fetchQueue addOperationWithBlock:^{
  316. NSNumber *currentFrameIndex = @(self.currentFrameIndex);
  317. SD_LOCK(self.lock);
  318. NSArray *keys = self.frameBuffer.allKeys;
  319. // only keep the next frame for later rendering
  320. for (NSNumber * key in keys) {
  321. if (![key isEqualToNumber:currentFrameIndex]) {
  322. [self.frameBuffer removeObjectForKey:key];
  323. }
  324. }
  325. SD_UNLOCK(self.lock);
  326. }];
  327. }
  328. #pragma mark - UIView Method Overrides
  329. #pragma mark Observing View-Related Changes
  330. #if SD_MAC
  331. - (void)viewDidMoveToSuperview
  332. #else
  333. - (void)didMoveToSuperview
  334. #endif
  335. {
  336. #if SD_MAC
  337. [super viewDidMoveToSuperview];
  338. #else
  339. [super didMoveToSuperview];
  340. #endif
  341. [self updateShouldAnimate];
  342. if (self.shouldAnimate) {
  343. [self startAnimating];
  344. } else {
  345. [self stopAnimating];
  346. }
  347. }
  348. #if SD_MAC
  349. - (void)viewDidMoveToWindow
  350. #else
  351. - (void)didMoveToWindow
  352. #endif
  353. {
  354. #if SD_MAC
  355. [super viewDidMoveToWindow];
  356. #else
  357. [super didMoveToWindow];
  358. #endif
  359. [self updateShouldAnimate];
  360. if (self.shouldAnimate) {
  361. [self startAnimating];
  362. } else {
  363. [self stopAnimating];
  364. }
  365. }
  366. #if SD_MAC
  367. - (void)setAlphaValue:(CGFloat)alphaValue
  368. #else
  369. - (void)setAlpha:(CGFloat)alpha
  370. #endif
  371. {
  372. #if SD_MAC
  373. [super setAlphaValue:alphaValue];
  374. #else
  375. [super setAlpha:alpha];
  376. #endif
  377. [self updateShouldAnimate];
  378. if (self.shouldAnimate) {
  379. [self startAnimating];
  380. } else {
  381. [self stopAnimating];
  382. }
  383. }
  384. - (void)setHidden:(BOOL)hidden
  385. {
  386. [super setHidden:hidden];
  387. [self updateShouldAnimate];
  388. if (self.shouldAnimate) {
  389. [self startAnimating];
  390. } else {
  391. [self stopAnimating];
  392. }
  393. }
  394. #pragma mark - UIImageView Method Overrides
  395. #pragma mark Image Data
  396. - (void)startAnimating
  397. {
  398. if (self.animatedImage) {
  399. #if SD_MAC
  400. CVDisplayLinkStart(self.displayLink);
  401. #else
  402. self.displayLink.paused = NO;
  403. #endif
  404. } else {
  405. #if SD_UIKIT
  406. [super startAnimating];
  407. #endif
  408. }
  409. }
  410. - (void)stopAnimating
  411. {
  412. if (self.animatedImage) {
  413. #if SD_MAC
  414. CVDisplayLinkStop(_displayLink);
  415. #else
  416. _displayLink.paused = YES;
  417. #endif
  418. } else {
  419. #if SD_UIKIT
  420. [super stopAnimating];
  421. #endif
  422. }
  423. }
  424. - (BOOL)isAnimating
  425. {
  426. BOOL isAnimating = NO;
  427. if (self.animatedImage) {
  428. #if SD_MAC
  429. isAnimating = CVDisplayLinkIsRunning(self.displayLink);
  430. #else
  431. isAnimating = !self.displayLink.isPaused;
  432. #endif
  433. } else {
  434. #if SD_UIKIT
  435. isAnimating = [super isAnimating];
  436. #endif
  437. }
  438. return isAnimating;
  439. }
  440. #if SD_MAC
  441. - (void)setAnimates:(BOOL)animates
  442. {
  443. [super setAnimates:animates];
  444. if (animates) {
  445. [self startAnimating];
  446. } else {
  447. [self stopAnimating];
  448. }
  449. }
  450. #endif
  451. #pragma mark Highlighted Image Unsupport
  452. - (void)setHighlighted:(BOOL)highlighted
  453. {
  454. // Highlighted image is unsupported for animated images, but implementing it breaks the image view when embedded in a UICollectionViewCell.
  455. if (!self.animatedImage) {
  456. [super setHighlighted:highlighted];
  457. }
  458. }
  459. #pragma mark - Private Methods
  460. #pragma mark Animation
  461. // Don't repeatedly check our window & superview in `-displayDidRefresh:` for performance reasons.
  462. // Just update our cached value whenever the animated image or visibility (window, superview, hidden, alpha) is changed.
  463. - (void)updateShouldAnimate
  464. {
  465. #if SD_MAC
  466. BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alphaValue > 0.0 && self.animates;
  467. #else
  468. BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alpha > 0.0;
  469. #endif
  470. self.shouldAnimate = self.animatedImage && self.totalFrameCount > 1 && isVisible;
  471. }
  472. // Update progressive status only after `setImage:` call.
  473. - (void)updateIsProgressiveWithImage:(UIImage *)image
  474. {
  475. self.isProgressive = NO;
  476. if (!self.shouldIncrementalLoad) {
  477. // Early return
  478. return;
  479. }
  480. if ([image conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental) {
  481. UIImage *previousImage = self.image;
  482. if ([previousImage conformsToProtocol:@protocol(SDAnimatedImage)] && previousImage.sd_isIncremental) {
  483. NSData *previousData = [((UIImage<SDAnimatedImage> *)previousImage) animatedImageData];
  484. NSData *currentData = [((UIImage<SDAnimatedImage> *)image) animatedImageData];
  485. // Check whether to use progressive rendering or not
  486. if (!previousData || !currentData) {
  487. // Early return
  488. return;
  489. }
  490. // 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`.
  491. // 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.
  492. if ([currentData isEqualToData:previousData]) {
  493. // If current data is the same data (or instance) as previous data
  494. self.isProgressive = YES;
  495. } else if (currentData.length > previousData.length) {
  496. // If current data is appended by previous data, use `NSDataSearchAnchored`, search is limited to start of currentData
  497. NSRange range = [currentData rangeOfData:previousData options:NSDataSearchAnchored range:NSMakeRange(0, previousData.length)];
  498. if (range.location != NSNotFound) {
  499. // Contains hole previous data and they start with the same beginning
  500. self.isProgressive = YES;
  501. }
  502. }
  503. } else {
  504. // Previous image is not progressive, so start progressive rendering
  505. self.isProgressive = YES;
  506. }
  507. }
  508. }
  509. #if SD_MAC
  510. - (void)displayDidRefresh:(CVDisplayLinkRef)displayLink duration:(NSTimeInterval)duration
  511. #else
  512. - (void)displayDidRefresh:(CADisplayLink *)displayLink
  513. #endif
  514. {
  515. // If for some reason a wild call makes it through when we shouldn't be animating, bail.
  516. // Early return!
  517. if (!self.shouldAnimate) {
  518. return;
  519. }
  520. #if SD_UIKIT
  521. NSTimeInterval duration = displayLink.duration * displayLink.frameInterval;
  522. #endif
  523. NSUInteger totalFrameCount = self.totalFrameCount;
  524. NSUInteger currentFrameIndex = self.currentFrameIndex;
  525. NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
  526. // Check if we have the frame buffer firstly to improve performance
  527. if (!self.bufferMiss) {
  528. // Then check if timestamp is reached
  529. self.currentTime += duration;
  530. NSTimeInterval currentDuration = [self.animatedImage animatedImageDurationAtIndex:currentFrameIndex];
  531. if (self.currentTime < currentDuration) {
  532. // Current frame timestamp not reached, return
  533. return;
  534. }
  535. self.currentTime -= currentDuration;
  536. NSTimeInterval nextDuration = [self.animatedImage animatedImageDurationAtIndex:nextFrameIndex];
  537. if (self.currentTime > nextDuration) {
  538. // Do not skip frame
  539. self.currentTime = nextDuration;
  540. }
  541. }
  542. // Update the current frame
  543. UIImage *currentFrame;
  544. UIImage *fetchFrame;
  545. SD_LOCK(self.lock);
  546. currentFrame = self.frameBuffer[@(currentFrameIndex)];
  547. fetchFrame = currentFrame ? self.frameBuffer[@(nextFrameIndex)] : nil;
  548. SD_UNLOCK(self.lock);
  549. BOOL bufferFull = NO;
  550. if (currentFrame) {
  551. SD_LOCK(self.lock);
  552. // Remove the frame buffer if need
  553. if (self.frameBuffer.count > self.maxBufferCount) {
  554. self.frameBuffer[@(currentFrameIndex)] = nil;
  555. }
  556. // Check whether we can stop fetch
  557. if (self.frameBuffer.count == totalFrameCount) {
  558. bufferFull = YES;
  559. }
  560. SD_UNLOCK(self.lock);
  561. self.currentFrame = currentFrame;
  562. self.currentFrameIndex = nextFrameIndex;
  563. self.bufferMiss = NO;
  564. [self.layer setNeedsDisplay];
  565. } else {
  566. self.bufferMiss = YES;
  567. }
  568. // Update the loop count when last frame rendered
  569. if (nextFrameIndex == 0 && !self.bufferMiss) {
  570. // Progressive image reach the current last frame index. Keep the state and stop animating. Wait for later restart
  571. if (self.isProgressive) {
  572. // Recovery the current frame index and removed frame buffer (See above)
  573. self.currentFrameIndex = currentFrameIndex;
  574. SD_LOCK(self.lock);
  575. self.frameBuffer[@(currentFrameIndex)] = self.currentFrame;
  576. SD_UNLOCK(self.lock);
  577. [self stopAnimating];
  578. return;
  579. }
  580. // Update the loop count
  581. self.currentLoopCount++;
  582. // if reached the max loop count, stop animating, 0 means loop indefinitely
  583. NSUInteger maxLoopCount = self.shouldCustomLoopCount ? self.animationRepeatCount : self.totalLoopCount;
  584. if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
  585. [self stopAnimating];
  586. return;
  587. }
  588. }
  589. // Check if we should prefetch next frame or current frame
  590. NSUInteger fetchFrameIndex;
  591. if (self.bufferMiss) {
  592. // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
  593. fetchFrameIndex = currentFrameIndex;
  594. } else {
  595. // Or, most cases, the decode speed is faster than render speed, we fetch next frame
  596. fetchFrameIndex = nextFrameIndex;
  597. }
  598. if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
  599. // Prefetch next frame in background queue
  600. UIImage<SDAnimatedImage> *animatedImage = self.animatedImage;
  601. NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
  602. UIImage *frame = [animatedImage animatedImageFrameAtIndex:fetchFrameIndex];
  603. SD_LOCK(self.lock);
  604. self.frameBuffer[@(fetchFrameIndex)] = frame;
  605. SD_UNLOCK(self.lock);
  606. }];
  607. [self.fetchQueue addOperation:operation];
  608. }
  609. }
  610. + (NSString *)defaultRunLoopMode
  611. {
  612. // Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations.
  613. return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
  614. }
  615. #pragma mark Providing the Layer's Content
  616. #pragma mark - CALayerDelegate
  617. - (void)displayLayer:(CALayer *)layer
  618. {
  619. if (_currentFrame) {
  620. layer.contentsScale = self.animatedImageScale;
  621. layer.contents = (__bridge id)_currentFrame.CGImage;
  622. }
  623. }
  624. #if SD_MAC
  625. // Layer-backed NSImageView optionally optimize to use a subview to do actual layer rendering.
  626. // When the optimization is turned on, it calls `updateLayer` instead of `displayLayer:` to update subview's layer.
  627. // When the optimization it turned off, this return nil and calls `displayLayer:` directly.
  628. - (CALayer *)imageViewLayer {
  629. NSView *imageView = imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageView"));
  630. if (!imageView) {
  631. // macOS 10.14
  632. imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageSubview"));
  633. }
  634. return imageView.layer;
  635. }
  636. - (void)updateLayer
  637. {
  638. if (_currentFrame) {
  639. [self displayLayer:self.imageViewLayer];
  640. } else {
  641. [super updateLayer];
  642. }
  643. }
  644. - (BOOL)wantsUpdateLayer {
  645. // AppKit is different from UIKit, it need extra check before the layer is updated
  646. // 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
  647. if (_currentFrame) {
  648. return NO;
  649. } else {
  650. return YES;
  651. }
  652. }
  653. #endif
  654. #pragma mark - Util
  655. - (void)calculateMaxBufferCount {
  656. NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
  657. if (bytes == 0) bytes = 1024;
  658. NSUInteger max = 0;
  659. if (self.maxBufferSize > 0) {
  660. max = self.maxBufferSize;
  661. } else {
  662. // Calculate based on current memory, these factors are by experience
  663. NSUInteger total = SDDeviceTotalMemory();
  664. NSUInteger free = SDDeviceFreeMemory();
  665. max = MIN(total * 0.2, free * 0.6);
  666. }
  667. NSUInteger maxBufferCount = (double)max / (double)bytes;
  668. if (!maxBufferCount) {
  669. // At least 1 frame
  670. maxBufferCount = 1;
  671. }
  672. self.maxBufferCount = maxBufferCount;
  673. }
  674. @end
  675. #if SD_MAC
  676. static CVReturn renderCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
  677. // Calculate refresh duration
  678. NSTimeInterval duration = (double)inOutputTime->videoRefreshPeriod / ((double)inOutputTime->videoTimeScale * inOutputTime->rateScalar);
  679. // CVDisplayLink callback is not on main queue
  680. SDAnimatedImageView *imageView = (__bridge SDAnimatedImageView *)displayLinkContext;
  681. __weak SDAnimatedImageView *weakImageView = imageView;
  682. dispatch_async(dispatch_get_main_queue(), ^{
  683. [weakImageView displayDidRefresh:displayLink duration:duration];
  684. });
  685. return kCVReturnSuccess;
  686. }
  687. #endif
  688. #endif