YYAnimatedImageView.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. //
  2. // YYAnimatedImageView.m
  3. // YYImage <https://github.com/ibireme/YYImage>
  4. //
  5. // Created by ibireme on 14/10/19.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYAnimatedImageView.h"
  12. #import "YYImageCoder.h"
  13. #import <pthread.h>
  14. #import <mach/mach.h>
  15. #define BUFFER_SIZE (10 * 1024 * 1024) // 10MB (minimum memory buffer size)
  16. #define LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \
  17. __VA_ARGS__; \
  18. dispatch_semaphore_signal(self->_lock);
  19. #define LOCK_VIEW(...) dispatch_semaphore_wait(view->_lock, DISPATCH_TIME_FOREVER); \
  20. __VA_ARGS__; \
  21. dispatch_semaphore_signal(view->_lock);
  22. static int64_t _YYDeviceMemoryTotal() {
  23. int64_t mem = [[NSProcessInfo processInfo] physicalMemory];
  24. if (mem < -1) mem = -1;
  25. return mem;
  26. }
  27. static int64_t _YYDeviceMemoryFree() {
  28. mach_port_t host_port = mach_host_self();
  29. mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
  30. vm_size_t page_size;
  31. vm_statistics_data_t vm_stat;
  32. kern_return_t kern;
  33. kern = host_page_size(host_port, &page_size);
  34. if (kern != KERN_SUCCESS) return -1;
  35. kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
  36. if (kern != KERN_SUCCESS) return -1;
  37. return vm_stat.free_count * page_size;
  38. }
  39. /**
  40. A proxy used to hold a weak object.
  41. It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink.
  42. */
  43. @interface _YYImageWeakProxy : NSProxy
  44. @property (nonatomic, weak, readonly) id target;
  45. - (instancetype)initWithTarget:(id)target;
  46. + (instancetype)proxyWithTarget:(id)target;
  47. @end
  48. @implementation _YYImageWeakProxy
  49. - (instancetype)initWithTarget:(id)target {
  50. _target = target;
  51. return self;
  52. }
  53. + (instancetype)proxyWithTarget:(id)target {
  54. return [[_YYImageWeakProxy alloc] initWithTarget:target];
  55. }
  56. - (id)forwardingTargetForSelector:(SEL)selector {
  57. return _target;
  58. }
  59. - (void)forwardInvocation:(NSInvocation *)invocation {
  60. void *null = NULL;
  61. [invocation setReturnValue:&null];
  62. }
  63. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  64. return [NSObject instanceMethodSignatureForSelector:@selector(init)];
  65. }
  66. - (BOOL)respondsToSelector:(SEL)aSelector {
  67. return [_target respondsToSelector:aSelector];
  68. }
  69. - (BOOL)isEqual:(id)object {
  70. return [_target isEqual:object];
  71. }
  72. - (NSUInteger)hash {
  73. return [_target hash];
  74. }
  75. - (Class)superclass {
  76. return [_target superclass];
  77. }
  78. - (Class)class {
  79. return [_target class];
  80. }
  81. - (BOOL)isKindOfClass:(Class)aClass {
  82. return [_target isKindOfClass:aClass];
  83. }
  84. - (BOOL)isMemberOfClass:(Class)aClass {
  85. return [_target isMemberOfClass:aClass];
  86. }
  87. - (BOOL)conformsToProtocol:(Protocol *)aProtocol {
  88. return [_target conformsToProtocol:aProtocol];
  89. }
  90. - (BOOL)isProxy {
  91. return YES;
  92. }
  93. - (NSString *)description {
  94. return [_target description];
  95. }
  96. - (NSString *)debugDescription {
  97. return [_target debugDescription];
  98. }
  99. @end
  100. typedef NS_ENUM(NSUInteger, YYAnimatedImageType) {
  101. YYAnimatedImageTypeNone = 0,
  102. YYAnimatedImageTypeImage,
  103. YYAnimatedImageTypeHighlightedImage,
  104. YYAnimatedImageTypeImages,
  105. YYAnimatedImageTypeHighlightedImages,
  106. };
  107. @interface YYAnimatedImageView() {
  108. @package
  109. UIImage <YYAnimatedImage> *_curAnimatedImage;
  110. dispatch_once_t _onceToken;
  111. dispatch_semaphore_t _lock; ///< lock for _buffer
  112. NSOperationQueue *_requestQueue; ///< image request queue, serial
  113. CADisplayLink *_link; ///< ticker for change frame
  114. NSTimeInterval _time; ///< time after last frame
  115. UIImage *_curFrame; ///< current frame to display
  116. NSUInteger _curIndex; ///< current frame index (from 0)
  117. NSUInteger _totalFrameCount; ///< total frame count
  118. BOOL _loopEnd; ///< whether the loop is end.
  119. NSUInteger _curLoop; ///< current loop count (from 0)
  120. NSUInteger _totalLoop; ///< total loop count, 0 means infinity
  121. NSMutableDictionary *_buffer; ///< frame buffer
  122. BOOL _bufferMiss; ///< whether miss frame on last opportunity
  123. NSUInteger _maxBufferCount; ///< maximum buffer count
  124. NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step)
  125. CGRect _curContentsRect;
  126. BOOL _curImageHasContentsRect; ///< image has implementated "animatedImageContentsRectAtIndex:"
  127. }
  128. @property (nonatomic, readwrite) BOOL currentIsPlayingAnimation;
  129. - (void)calcMaxBufferCount;
  130. @end
  131. /// An operation for image fetch
  132. @interface _YYAnimatedImageViewFetchOperation : NSOperation
  133. @property (nonatomic, weak) YYAnimatedImageView *view;
  134. @property (nonatomic, assign) NSUInteger nextIndex;
  135. @property (nonatomic, strong) UIImage <YYAnimatedImage> *curImage;
  136. @end
  137. @implementation _YYAnimatedImageViewFetchOperation
  138. - (void)main {
  139. __strong YYAnimatedImageView *view = _view;
  140. if (!view) return;
  141. if ([self isCancelled]) return;
  142. view->_incrBufferCount++;
  143. if (view->_incrBufferCount == 0) [view calcMaxBufferCount];
  144. if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) {
  145. view->_incrBufferCount = view->_maxBufferCount;
  146. }
  147. NSUInteger idx = _nextIndex;
  148. NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount;
  149. NSUInteger total = view->_totalFrameCount;
  150. view = nil;
  151. for (int i = 0; i < max; i++, idx++) {
  152. @autoreleasepool {
  153. if (idx >= total) idx = 0;
  154. if ([self isCancelled]) break;
  155. __strong YYAnimatedImageView *view = _view;
  156. if (!view) break;
  157. LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil));
  158. if (miss) {
  159. UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
  160. img = img.yy_imageByDecoded;
  161. if ([self isCancelled]) break;
  162. LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
  163. view = nil;
  164. }
  165. }
  166. }
  167. }
  168. @end
  169. @implementation YYAnimatedImageView
  170. - (instancetype)init {
  171. self = [super init];
  172. _runloopMode = NSRunLoopCommonModes;
  173. _autoPlayAnimatedImage = YES;
  174. return self;
  175. }
  176. - (instancetype)initWithFrame:(CGRect)frame {
  177. self = [super initWithFrame:frame];
  178. _runloopMode = NSRunLoopCommonModes;
  179. _autoPlayAnimatedImage = YES;
  180. return self;
  181. }
  182. - (instancetype)initWithImage:(UIImage *)image {
  183. self = [super init];
  184. _runloopMode = NSRunLoopCommonModes;
  185. _autoPlayAnimatedImage = YES;
  186. self.frame = (CGRect) {CGPointZero, image.size };
  187. self.image = image;
  188. return self;
  189. }
  190. - (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage {
  191. self = [super init];
  192. _runloopMode = NSRunLoopCommonModes;
  193. _autoPlayAnimatedImage = YES;
  194. CGSize size = image ? image.size : highlightedImage.size;
  195. self.frame = (CGRect) {CGPointZero, size };
  196. self.image = image;
  197. self.highlightedImage = highlightedImage;
  198. return self;
  199. }
  200. // init the animated params.
  201. - (void)resetAnimated {
  202. dispatch_once(&_onceToken, ^{
  203. _lock = dispatch_semaphore_create(1);
  204. _buffer = [NSMutableDictionary new];
  205. _requestQueue = [[NSOperationQueue alloc] init];
  206. _requestQueue.maxConcurrentOperationCount = 1;
  207. _link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)];
  208. if (_runloopMode) {
  209. [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
  210. }
  211. _link.paused = YES;
  212. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  213. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
  214. });
  215. [_requestQueue cancelAllOperations];
  216. LOCK(
  217. if (_buffer.count) {
  218. NSMutableDictionary *holder = _buffer;
  219. _buffer = [NSMutableDictionary new];
  220. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  221. // Capture the dictionary to global queue,
  222. // release these images in background to avoid blocking UI thread.
  223. [holder class];
  224. });
  225. }
  226. );
  227. _link.paused = YES;
  228. _time = 0;
  229. if (_curIndex != 0) {
  230. [self willChangeValueForKey:@"currentAnimatedImageIndex"];
  231. _curIndex = 0;
  232. [self didChangeValueForKey:@"currentAnimatedImageIndex"];
  233. }
  234. _curAnimatedImage = nil;
  235. _curFrame = nil;
  236. _curLoop = 0;
  237. _totalLoop = 0;
  238. _totalFrameCount = 1;
  239. _loopEnd = NO;
  240. _bufferMiss = NO;
  241. _incrBufferCount = 0;
  242. }
  243. - (void)setImage:(UIImage *)image {
  244. if (self.image == image) return;
  245. [self setImage:image withType:YYAnimatedImageTypeImage];
  246. }
  247. - (void)setHighlightedImage:(UIImage *)highlightedImage {
  248. if (self.highlightedImage == highlightedImage) return;
  249. [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
  250. }
  251. - (void)setAnimationImages:(NSArray *)animationImages {
  252. if (self.animationImages == animationImages) return;
  253. [self setImage:animationImages withType:YYAnimatedImageTypeImages];
  254. }
  255. - (void)setHighlightedAnimationImages:(NSArray *)highlightedAnimationImages {
  256. if (self.highlightedAnimationImages == highlightedAnimationImages) return;
  257. [self setImage:highlightedAnimationImages withType:YYAnimatedImageTypeHighlightedImages];
  258. }
  259. - (void)setHighlighted:(BOOL)highlighted {
  260. [super setHighlighted:highlighted];
  261. if (_link) [self resetAnimated];
  262. [self imageChanged];
  263. }
  264. - (id)imageForType:(YYAnimatedImageType)type {
  265. switch (type) {
  266. case YYAnimatedImageTypeNone: return nil;
  267. case YYAnimatedImageTypeImage: return self.image;
  268. case YYAnimatedImageTypeHighlightedImage: return self.highlightedImage;
  269. case YYAnimatedImageTypeImages: return self.animationImages;
  270. case YYAnimatedImageTypeHighlightedImages: return self.highlightedAnimationImages;
  271. }
  272. return nil;
  273. }
  274. - (YYAnimatedImageType)currentImageType {
  275. YYAnimatedImageType curType = YYAnimatedImageTypeNone;
  276. if (self.highlighted) {
  277. if (self.highlightedAnimationImages.count) curType = YYAnimatedImageTypeHighlightedImages;
  278. else if (self.highlightedImage) curType = YYAnimatedImageTypeHighlightedImage;
  279. }
  280. if (curType == YYAnimatedImageTypeNone) {
  281. if (self.animationImages.count) curType = YYAnimatedImageTypeImages;
  282. else if (self.image) curType = YYAnimatedImageTypeImage;
  283. }
  284. return curType;
  285. }
  286. - (void)setImage:(id)image withType:(YYAnimatedImageType)type {
  287. [self stopAnimating];
  288. if (_link) [self resetAnimated];
  289. _curFrame = nil;
  290. switch (type) {
  291. case YYAnimatedImageTypeNone: break;
  292. case YYAnimatedImageTypeImage: super.image = image; break;
  293. case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break;
  294. case YYAnimatedImageTypeImages: super.animationImages = image; break;
  295. case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break;
  296. }
  297. [self imageChanged];
  298. }
  299. - (void)imageChanged {
  300. YYAnimatedImageType newType = [self currentImageType];
  301. id newVisibleImage = [self imageForType:newType];
  302. NSUInteger newImageFrameCount = 0;
  303. BOOL hasContentsRect = NO;
  304. if ([newVisibleImage isKindOfClass:[UIImage class]] &&
  305. [newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) {
  306. newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount;
  307. if (newImageFrameCount > 1) {
  308. hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)];
  309. }
  310. }
  311. if (!hasContentsRect && _curImageHasContentsRect) {
  312. if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
  313. [CATransaction begin];
  314. [CATransaction setDisableActions:YES];
  315. self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
  316. [CATransaction commit];
  317. }
  318. }
  319. _curImageHasContentsRect = hasContentsRect;
  320. if (hasContentsRect) {
  321. CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
  322. [self setContentsRect:rect forImage:newVisibleImage];
  323. }
  324. if (newImageFrameCount > 1) {
  325. [self resetAnimated];
  326. _curAnimatedImage = newVisibleImage;
  327. _curFrame = newVisibleImage;
  328. _totalLoop = _curAnimatedImage.animatedImageLoopCount;
  329. _totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
  330. [self calcMaxBufferCount];
  331. }
  332. [self setNeedsDisplay];
  333. [self didMoved];
  334. }
  335. // dynamically adjust buffer size for current memory.
  336. - (void)calcMaxBufferCount {
  337. int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame;
  338. if (bytes == 0) bytes = 1024;
  339. int64_t total = _YYDeviceMemoryTotal();
  340. int64_t free = _YYDeviceMemoryFree();
  341. int64_t max = MIN(total * 0.2, free * 0.6);
  342. max = MAX(max, BUFFER_SIZE);
  343. if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;
  344. double maxBufferCount = (double)max / (double)bytes;
  345. if (maxBufferCount < 1) maxBufferCount = 1;
  346. else if (maxBufferCount > 512) maxBufferCount = 512;
  347. _maxBufferCount = maxBufferCount;
  348. }
  349. - (void)dealloc {
  350. [_requestQueue cancelAllOperations];
  351. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  352. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
  353. [_link invalidate];
  354. }
  355. - (BOOL)isAnimating {
  356. return self.currentIsPlayingAnimation;
  357. }
  358. - (void)stopAnimating {
  359. [super stopAnimating];
  360. [_requestQueue cancelAllOperations];
  361. _link.paused = YES;
  362. self.currentIsPlayingAnimation = NO;
  363. }
  364. - (void)startAnimating {
  365. YYAnimatedImageType type = [self currentImageType];
  366. if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) {
  367. NSArray *images = [self imageForType:type];
  368. if (images.count > 0) {
  369. [super startAnimating];
  370. self.currentIsPlayingAnimation = YES;
  371. }
  372. } else {
  373. if (_curAnimatedImage && _link.paused) {
  374. _curLoop = 0;
  375. _loopEnd = NO;
  376. _link.paused = NO;
  377. self.currentIsPlayingAnimation = YES;
  378. }
  379. }
  380. }
  381. - (void)didReceiveMemoryWarning:(NSNotification *)notification {
  382. [_requestQueue cancelAllOperations];
  383. [_requestQueue addOperationWithBlock: ^{
  384. _incrBufferCount = -60 - (int)(arc4random() % 120); // about 1~3 seconds to grow back..
  385. NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
  386. LOCK(
  387. NSArray * keys = _buffer.allKeys;
  388. for (NSNumber * key in keys) {
  389. if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
  390. [_buffer removeObjectForKey:key];
  391. }
  392. }
  393. )//LOCK
  394. }];
  395. }
  396. - (void)didEnterBackground:(NSNotification *)notification {
  397. [_requestQueue cancelAllOperations];
  398. NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
  399. LOCK(
  400. NSArray * keys = _buffer.allKeys;
  401. for (NSNumber * key in keys) {
  402. if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
  403. [_buffer removeObjectForKey:key];
  404. }
  405. }
  406. )//LOCK
  407. }
  408. - (void)step:(CADisplayLink *)link {
  409. UIImage <YYAnimatedImage> *image = _curAnimatedImage;
  410. NSMutableDictionary *buffer = _buffer;
  411. UIImage *bufferedImage = nil;
  412. NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;
  413. BOOL bufferIsFull = NO;
  414. if (!image) return;
  415. if (_loopEnd) { // view will keep in last frame
  416. [self stopAnimating];
  417. return;
  418. }
  419. NSTimeInterval delay = 0;
  420. if (!_bufferMiss) {
  421. _time += link.duration;
  422. delay = [image animatedImageDurationAtIndex:_curIndex];
  423. if (_time < delay) return;
  424. _time -= delay;
  425. if (nextIndex == 0) {
  426. _curLoop++;
  427. if (_curLoop >= _totalLoop && _totalLoop != 0) {
  428. _loopEnd = YES;
  429. [self stopAnimating];
  430. [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
  431. return; // stop at last frame
  432. }
  433. }
  434. delay = [image animatedImageDurationAtIndex:nextIndex];
  435. if (_time > delay) _time = delay; // do not jump over frame
  436. }
  437. LOCK(
  438. bufferedImage = buffer[@(nextIndex)];
  439. if (bufferedImage) {
  440. if ((int)_incrBufferCount < _totalFrameCount) {
  441. [buffer removeObjectForKey:@(nextIndex)];
  442. }
  443. [self willChangeValueForKey:@"currentAnimatedImageIndex"];
  444. _curIndex = nextIndex;
  445. [self didChangeValueForKey:@"currentAnimatedImageIndex"];
  446. _curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;
  447. if (_curImageHasContentsRect) {
  448. _curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex];
  449. [self setContentsRect:_curContentsRect forImage:_curFrame];
  450. }
  451. nextIndex = (_curIndex + 1) % _totalFrameCount;
  452. _bufferMiss = NO;
  453. if (buffer.count == _totalFrameCount) {
  454. bufferIsFull = YES;
  455. }
  456. } else {
  457. _bufferMiss = YES;
  458. }
  459. )//LOCK
  460. if (!_bufferMiss) {
  461. [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
  462. }
  463. if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity
  464. _YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
  465. operation.view = self;
  466. operation.nextIndex = nextIndex;
  467. operation.curImage = image;
  468. [_requestQueue addOperation:operation];
  469. }
  470. }
  471. - (void)displayLayer:(CALayer *)layer {
  472. if (_curFrame) {
  473. layer.contents = (__bridge id)_curFrame.CGImage;
  474. }
  475. }
  476. - (void)setContentsRect:(CGRect)rect forImage:(UIImage *)image{
  477. CGRect layerRect = CGRectMake(0, 0, 1, 1);
  478. if (image) {
  479. CGSize imageSize = image.size;
  480. if (imageSize.width > 0.01 && imageSize.height > 0.01) {
  481. layerRect.origin.x = rect.origin.x / imageSize.width;
  482. layerRect.origin.y = rect.origin.y / imageSize.height;
  483. layerRect.size.width = rect.size.width / imageSize.width;
  484. layerRect.size.height = rect.size.height / imageSize.height;
  485. layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1));
  486. if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) {
  487. layerRect = CGRectMake(0, 0, 1, 1);
  488. }
  489. }
  490. }
  491. [CATransaction begin];
  492. [CATransaction setDisableActions:YES];
  493. self.layer.contentsRect = layerRect;
  494. [CATransaction commit];
  495. }
  496. - (void)didMoved {
  497. if (self.autoPlayAnimatedImage) {
  498. if(self.superview && self.window) {
  499. [self startAnimating];
  500. } else {
  501. [self stopAnimating];
  502. }
  503. }
  504. }
  505. - (void)didMoveToWindow {
  506. [super didMoveToWindow];
  507. [self didMoved];
  508. }
  509. - (void)didMoveToSuperview {
  510. [super didMoveToSuperview];
  511. [self didMoved];
  512. }
  513. - (void)setCurrentAnimatedImageIndex:(NSUInteger)currentAnimatedImageIndex {
  514. if (!_curAnimatedImage) return;
  515. if (currentAnimatedImageIndex >= _curAnimatedImage.animatedImageFrameCount) return;
  516. if (_curIndex == currentAnimatedImageIndex) return;
  517. void (^block)() = ^{
  518. LOCK(
  519. [_requestQueue cancelAllOperations];
  520. [_buffer removeAllObjects];
  521. [self willChangeValueForKey:@"currentAnimatedImageIndex"];
  522. _curIndex = currentAnimatedImageIndex;
  523. [self didChangeValueForKey:@"currentAnimatedImageIndex"];
  524. _curFrame = [_curAnimatedImage animatedImageFrameAtIndex:_curIndex];
  525. if (_curImageHasContentsRect) {
  526. _curContentsRect = [_curAnimatedImage animatedImageContentsRectAtIndex:_curIndex];
  527. }
  528. _time = 0;
  529. _loopEnd = NO;
  530. _bufferMiss = NO;
  531. [self.layer setNeedsDisplay];
  532. )//LOCK
  533. };
  534. if (pthread_main_np()) {
  535. block();
  536. } else {
  537. dispatch_async(dispatch_get_main_queue(), block);
  538. }
  539. }
  540. - (NSUInteger)currentAnimatedImageIndex {
  541. return _curIndex;
  542. }
  543. - (void)setRunloopMode:(NSString *)runloopMode {
  544. if ([_runloopMode isEqual:runloopMode]) return;
  545. if (_link) {
  546. if (_runloopMode) {
  547. [_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
  548. }
  549. if (runloopMode.length) {
  550. [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:runloopMode];
  551. }
  552. }
  553. _runloopMode = runloopMode.copy;
  554. }
  555. #pragma mark - Overrice NSObject(NSKeyValueObservingCustomization)
  556. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  557. if ([key isEqualToString:@"currentAnimatedImageIndex"]) {
  558. return NO;
  559. }
  560. return [super automaticallyNotifiesObserversForKey:key];
  561. }
  562. #pragma mark - NSCoding
  563. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  564. self = [super initWithCoder:aDecoder];
  565. _runloopMode = [aDecoder decodeObjectForKey:@"runloopMode"];
  566. if (_runloopMode.length == 0) _runloopMode = NSRunLoopCommonModes;
  567. if ([aDecoder containsValueForKey:@"autoPlayAnimatedImage"]) {
  568. _autoPlayAnimatedImage = [aDecoder decodeBoolForKey:@"autoPlayAnimatedImage"];
  569. } else {
  570. _autoPlayAnimatedImage = YES;
  571. }
  572. UIImage *image = [aDecoder decodeObjectForKey:@"YYAnimatedImage"];
  573. UIImage *highlightedImage = [aDecoder decodeObjectForKey:@"YYHighlightedAnimatedImage"];
  574. if (image) {
  575. self.image = image;
  576. [self setImage:image withType:YYAnimatedImageTypeImage];
  577. }
  578. if (highlightedImage) {
  579. self.highlightedImage = highlightedImage;
  580. [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
  581. }
  582. return self;
  583. }
  584. - (void)encodeWithCoder:(NSCoder *)aCoder {
  585. [super encodeWithCoder:aCoder];
  586. [aCoder encodeObject:_runloopMode forKey:@"runloopMode"];
  587. [aCoder encodeBool:_autoPlayAnimatedImage forKey:@"autoPlayAnimatedImage"];
  588. BOOL ani, multi;
  589. ani = [self.image conformsToProtocol:@protocol(YYAnimatedImage)];
  590. multi = (ani && ((UIImage <YYAnimatedImage> *)self.image).animatedImageFrameCount > 1);
  591. if (multi) [aCoder encodeObject:self.image forKey:@"YYAnimatedImage"];
  592. ani = [self.highlightedImage conformsToProtocol:@protocol(YYAnimatedImage)];
  593. multi = (ani && ((UIImage <YYAnimatedImage> *)self.highlightedImage).animatedImageFrameCount > 1);
  594. if (multi) [aCoder encodeObject:self.highlightedImage forKey:@"YYHighlightedAnimatedImage"];
  595. }
  596. @end