RCTUIImageViewAnimated.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/RCTUIImageViewAnimated.h>
  8. #import <React/RCTDisplayWeakRefreshable.h>
  9. #import <mach/mach.h>
  10. #import <objc/runtime.h>
  11. static NSUInteger RCTDeviceTotalMemory() {
  12. return (NSUInteger)[[NSProcessInfo processInfo] physicalMemory];
  13. }
  14. static NSUInteger RCTDeviceFreeMemory() {
  15. mach_port_t host_port = mach_host_self();
  16. mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
  17. vm_size_t page_size;
  18. vm_statistics_data_t vm_stat;
  19. kern_return_t kern;
  20. kern = host_page_size(host_port, &page_size);
  21. if (kern != KERN_SUCCESS) return 0;
  22. kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
  23. if (kern != KERN_SUCCESS) return 0;
  24. return (vm_stat.free_count - vm_stat.speculative_count) * page_size;
  25. }
  26. @interface RCTUIImageViewAnimated () <CALayerDelegate, RCTDisplayRefreshable>
  27. @property (nonatomic, assign) NSUInteger maxBufferSize;
  28. @property (nonatomic, strong, readwrite) UIImage *currentFrame;
  29. @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
  30. @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
  31. @property (nonatomic, assign) NSUInteger totalFrameCount;
  32. @property (nonatomic, assign) NSUInteger totalLoopCount;
  33. @property (nonatomic, strong) UIImage<RCTAnimatedImage> *animatedImage;
  34. @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
  35. @property (nonatomic, assign) NSTimeInterval currentTime;
  36. @property (nonatomic, assign) BOOL bufferMiss;
  37. @property (nonatomic, assign) NSUInteger maxBufferCount;
  38. @property (nonatomic, strong) NSOperationQueue *fetchQueue;
  39. @property (nonatomic, strong) dispatch_semaphore_t lock;
  40. @property (nonatomic, assign) CGFloat animatedImageScale;
  41. @property (nonatomic, strong) CADisplayLink *displayLink;
  42. @end
  43. @implementation RCTUIImageViewAnimated
  44. - (instancetype)initWithFrame:(CGRect)frame
  45. {
  46. if (self = [super initWithFrame:frame]) {
  47. self.lock = dispatch_semaphore_create(1);
  48. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  49. }
  50. return self;
  51. }
  52. - (void)resetAnimatedImage
  53. {
  54. self.animatedImage = nil;
  55. self.totalFrameCount = 0;
  56. self.totalLoopCount = 0;
  57. self.currentFrame = nil;
  58. self.currentFrameIndex = 0;
  59. self.currentLoopCount = 0;
  60. self.currentTime = 0;
  61. self.bufferMiss = NO;
  62. self.maxBufferCount = 0;
  63. self.animatedImageScale = 1;
  64. [_fetchQueue cancelAllOperations];
  65. _fetchQueue = nil;
  66. dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
  67. [_frameBuffer removeAllObjects];
  68. _frameBuffer = nil;
  69. dispatch_semaphore_signal(self.lock);
  70. }
  71. - (void)setImage:(UIImage *)image
  72. {
  73. if (self.image == image) {
  74. return;
  75. }
  76. [self stop];
  77. [self resetAnimatedImage];
  78. if ([image respondsToSelector:@selector(animatedImageFrameAtIndex:)]) {
  79. NSUInteger animatedImageFrameCount = ((UIImage<RCTAnimatedImage> *)image).animatedImageFrameCount;
  80. // In case frame count is 0, there is no reason to continue.
  81. if (animatedImageFrameCount == 0) {
  82. return;
  83. }
  84. self.animatedImage = (UIImage<RCTAnimatedImage> *)image;
  85. self.totalFrameCount = animatedImageFrameCount;
  86. // Get the current frame and loop count.
  87. self.totalLoopCount = self.animatedImage.animatedImageLoopCount;
  88. self.animatedImageScale = image.scale;
  89. self.currentFrame = image;
  90. dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
  91. self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
  92. dispatch_semaphore_signal(self.lock);
  93. // Calculate max buffer size
  94. [self calculateMaxBufferCount];
  95. if ([self paused]) {
  96. [self start];
  97. }
  98. [self.layer setNeedsDisplay];
  99. } else {
  100. super.image = image;
  101. }
  102. }
  103. #pragma mark - Private
  104. - (NSOperationQueue *)fetchQueue
  105. {
  106. if (!_fetchQueue) {
  107. _fetchQueue = [[NSOperationQueue alloc] init];
  108. _fetchQueue.maxConcurrentOperationCount = 1;
  109. }
  110. return _fetchQueue;
  111. }
  112. - (NSMutableDictionary<NSNumber *,UIImage *> *)frameBuffer
  113. {
  114. if (!_frameBuffer) {
  115. _frameBuffer = [NSMutableDictionary dictionary];
  116. }
  117. return _frameBuffer;
  118. }
  119. - (CADisplayLink *)displayLink
  120. {
  121. // We only need a displayLink in the case of animated images, so short-circuit this code and don't create one for most of the use cases.
  122. // Since this class is used for all RCTImageView's, this is especially important.
  123. if (!_animatedImage) {
  124. return nil;
  125. }
  126. if (!_displayLink) {
  127. _displayLink = [RCTDisplayWeakRefreshable displayLinkWithWeakRefreshable:self];
  128. NSString *runLoopMode = [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
  129. [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
  130. }
  131. return _displayLink;
  132. }
  133. #pragma mark - Animation
  134. - (void)start
  135. {
  136. self.displayLink.paused = NO;
  137. }
  138. - (void)stop
  139. {
  140. self.displayLink.paused = YES;
  141. }
  142. - (BOOL)paused
  143. {
  144. return self.displayLink.isPaused;
  145. }
  146. - (void)displayDidRefresh:(CADisplayLink *)displayLink
  147. {
  148. #if TARGET_OS_UIKITFORMAC
  149. // TODO: `displayLink.frameInterval` is not available on UIKitForMac
  150. NSTimeInterval duration = displayLink.duration;
  151. #else
  152. NSTimeInterval duration = displayLink.duration * displayLink.frameInterval;
  153. #endif
  154. NSUInteger totalFrameCount = self.totalFrameCount;
  155. NSUInteger currentFrameIndex = self.currentFrameIndex;
  156. NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
  157. // Check if we have the frame buffer firstly to improve performance
  158. if (!self.bufferMiss) {
  159. // Then check if timestamp is reached
  160. self.currentTime += duration;
  161. NSTimeInterval currentDuration = [self.animatedImage animatedImageDurationAtIndex:currentFrameIndex];
  162. if (self.currentTime < currentDuration) {
  163. // Current frame timestamp not reached, return
  164. return;
  165. }
  166. self.currentTime -= currentDuration;
  167. NSTimeInterval nextDuration = [self.animatedImage animatedImageDurationAtIndex:nextFrameIndex];
  168. if (self.currentTime > nextDuration) {
  169. // Do not skip frame
  170. self.currentTime = nextDuration;
  171. }
  172. }
  173. // Update the current frame
  174. UIImage *currentFrame;
  175. UIImage *fetchFrame;
  176. dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
  177. currentFrame = self.frameBuffer[@(currentFrameIndex)];
  178. fetchFrame = currentFrame ? self.frameBuffer[@(nextFrameIndex)] : nil;
  179. dispatch_semaphore_signal(self.lock);
  180. BOOL bufferFull = NO;
  181. if (currentFrame) {
  182. dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
  183. // Remove the frame buffer if need
  184. if (self.frameBuffer.count > self.maxBufferCount) {
  185. self.frameBuffer[@(currentFrameIndex)] = nil;
  186. }
  187. // Check whether we can stop fetch
  188. if (self.frameBuffer.count == totalFrameCount) {
  189. bufferFull = YES;
  190. }
  191. dispatch_semaphore_signal(self.lock);
  192. self.currentFrame = currentFrame;
  193. self.currentFrameIndex = nextFrameIndex;
  194. self.bufferMiss = NO;
  195. [self.layer setNeedsDisplay];
  196. } else {
  197. self.bufferMiss = YES;
  198. }
  199. // Update the loop count when last frame rendered
  200. if (nextFrameIndex == 0 && !self.bufferMiss) {
  201. // Update the loop count
  202. self.currentLoopCount++;
  203. // if reached the max loop count, stop animating, 0 means loop indefinitely
  204. NSUInteger maxLoopCount = self.totalLoopCount;
  205. if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
  206. [self stop];
  207. return;
  208. }
  209. }
  210. // Check if we should prefetch next frame or current frame
  211. NSUInteger fetchFrameIndex;
  212. if (self.bufferMiss) {
  213. // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
  214. fetchFrameIndex = currentFrameIndex;
  215. } else {
  216. // Or, most cases, the decode speed is faster than render speed, we fetch next frame
  217. fetchFrameIndex = nextFrameIndex;
  218. }
  219. if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
  220. // Prefetch next frame in background queue
  221. UIImage<RCTAnimatedImage> *animatedImage = self.animatedImage;
  222. NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
  223. UIImage *frame = [animatedImage animatedImageFrameAtIndex:fetchFrameIndex];
  224. dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
  225. self.frameBuffer[@(fetchFrameIndex)] = frame;
  226. dispatch_semaphore_signal(self.lock);
  227. }];
  228. [self.fetchQueue addOperation:operation];
  229. }
  230. }
  231. #pragma mark - CALayerDelegate
  232. - (void)displayLayer:(CALayer *)layer
  233. {
  234. if (_currentFrame) {
  235. layer.contentsScale = self.animatedImageScale;
  236. layer.contents = (__bridge id)_currentFrame.CGImage;
  237. } else {
  238. [super displayLayer:layer];
  239. }
  240. }
  241. #pragma mark - Util
  242. - (void)calculateMaxBufferCount
  243. {
  244. NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
  245. if (bytes == 0) bytes = 1024;
  246. NSUInteger max = 0;
  247. if (self.maxBufferSize > 0) {
  248. max = self.maxBufferSize;
  249. } else {
  250. // Calculate based on current memory, these factors are by experience
  251. NSUInteger total = RCTDeviceTotalMemory();
  252. NSUInteger free = RCTDeviceFreeMemory();
  253. max = MIN(total * 0.2, free * 0.6);
  254. }
  255. NSUInteger maxBufferCount = (double)max / (double)bytes;
  256. if (!maxBufferCount) {
  257. // At least 1 frame
  258. maxBufferCount = 1;
  259. }
  260. self.maxBufferCount = maxBufferCount;
  261. }
  262. #pragma mark - Lifecycle
  263. - (void)dealloc
  264. {
  265. // Removes the display link from all run loop modes.
  266. [_displayLink invalidate];
  267. _displayLink = nil;
  268. }
  269. - (void)didReceiveMemoryWarning:(NSNotification *)notification
  270. {
  271. [_fetchQueue cancelAllOperations];
  272. [_fetchQueue addOperationWithBlock:^{
  273. NSNumber *currentFrameIndex = @(self.currentFrameIndex);
  274. dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
  275. NSArray *keys = self.frameBuffer.allKeys;
  276. // only keep the next frame for later rendering
  277. for (NSNumber * key in keys) {
  278. if (![key isEqualToNumber:currentFrameIndex]) {
  279. [self.frameBuffer removeObjectForKey:key];
  280. }
  281. }
  282. dispatch_semaphore_signal(self.lock);
  283. }];
  284. }
  285. @end