SDImageFramePool.m 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 "SDImageFramePool.h"
  9. #import "SDInternalMacros.h"
  10. #import "objc/runtime.h"
  11. @interface SDImageFramePool ()
  12. @property (class, readonly) NSMapTable *providerFramePoolMap;
  13. @property (weak) id<SDAnimatedImageProvider> provider;
  14. @property (atomic) NSUInteger registerCount;
  15. @property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
  16. @property (nonatomic, strong) NSOperationQueue *fetchQueue;
  17. @end
  18. // Lock to ensure atomic behavior
  19. SD_LOCK_DECLARE_STATIC(_providerFramePoolMapLock);
  20. @implementation SDImageFramePool
  21. + (NSMapTable *)providerFramePoolMap {
  22. static NSMapTable *providerFramePoolMap;
  23. static dispatch_once_t onceToken;
  24. dispatch_once(&onceToken, ^{
  25. // Key use `hash` && `isEqual:`
  26. providerFramePoolMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality];
  27. });
  28. return providerFramePoolMap;
  29. }
  30. #pragma mark - Life Cycle
  31. - (instancetype)init {
  32. self = [super init];
  33. if (self) {
  34. _frameBuffer = [NSMutableDictionary dictionary];
  35. _fetchQueue = [[NSOperationQueue alloc] init];
  36. _fetchQueue.maxConcurrentOperationCount = 1;
  37. _fetchQueue.name = @"com.hackemist.SDImageFramePool.fetchQueue";
  38. #if SD_UIKIT
  39. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  40. #endif
  41. }
  42. return self;
  43. }
  44. - (void)dealloc {
  45. #if SD_UIKIT
  46. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  47. #endif
  48. }
  49. - (void)didReceiveMemoryWarning:(NSNotification *)notification {
  50. [self removeAllFrames];
  51. }
  52. + (void)initialize {
  53. // Lock to ensure atomic behavior
  54. SD_LOCK_INIT(_providerFramePoolMapLock);
  55. }
  56. + (instancetype)registerProvider:(id<SDAnimatedImageProvider>)provider {
  57. // Lock to ensure atomic behavior
  58. SD_LOCK(_providerFramePoolMapLock);
  59. SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
  60. if (!framePool) {
  61. framePool = [[SDImageFramePool alloc] init];
  62. framePool.provider = provider;
  63. [self.providerFramePoolMap setObject:framePool forKey:provider];
  64. }
  65. framePool.registerCount += 1;
  66. SD_UNLOCK(_providerFramePoolMapLock);
  67. return framePool;
  68. }
  69. + (void)unregisterProvider:(id<SDAnimatedImageProvider>)provider {
  70. // Lock to ensure atomic behavior
  71. SD_LOCK(_providerFramePoolMapLock);
  72. SDImageFramePool *framePool = [self.providerFramePoolMap objectForKey:provider];
  73. if (!framePool) {
  74. SD_UNLOCK(_providerFramePoolMapLock);
  75. return;
  76. }
  77. framePool.registerCount -= 1;
  78. if (framePool.registerCount == 0) {
  79. [self.providerFramePoolMap removeObjectForKey:provider];
  80. }
  81. SD_UNLOCK(_providerFramePoolMapLock);
  82. }
  83. - (void)prefetchFrameAtIndex:(NSUInteger)index {
  84. @synchronized (self) {
  85. NSUInteger frameCount = self.frameBuffer.count;
  86. if (frameCount > self.maxBufferCount) {
  87. // Remove the frame buffer if need
  88. // TODO, use LRU or better algorithm to detect which frames to clear
  89. self.frameBuffer[@(index - 1)] = nil;
  90. self.frameBuffer[@(index + 1)] = nil;
  91. }
  92. }
  93. if (self.fetchQueue.operationCount == 0) {
  94. // Prefetch next frame in background queue
  95. id<SDAnimatedImageProvider> animatedProvider = self.provider;
  96. @weakify(self);
  97. NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
  98. @strongify(self);
  99. if (!self) {
  100. return;
  101. }
  102. UIImage *frame = [animatedProvider animatedImageFrameAtIndex:index];
  103. [self setFrame:frame atIndex:index];
  104. }];
  105. [self.fetchQueue addOperation:operation];
  106. }
  107. }
  108. - (void)setMaxConcurrentCount:(NSUInteger)maxConcurrentCount {
  109. self.fetchQueue.maxConcurrentOperationCount = maxConcurrentCount;
  110. }
  111. - (NSUInteger)currentFrameCount {
  112. NSUInteger frameCount = 0;
  113. @synchronized (self) {
  114. frameCount = self.frameBuffer.count;
  115. }
  116. return frameCount;
  117. }
  118. - (void)setFrame:(UIImage *)frame atIndex:(NSUInteger)index {
  119. @synchronized (self) {
  120. self.frameBuffer[@(index)] = frame;
  121. }
  122. }
  123. - (UIImage *)frameAtIndex:(NSUInteger)index {
  124. UIImage *frame;
  125. @synchronized (self) {
  126. frame = self.frameBuffer[@(index)];
  127. }
  128. return frame;
  129. }
  130. - (void)removeFrameAtIndex:(NSUInteger)index {
  131. @synchronized (self) {
  132. self.frameBuffer[@(index)] = nil;
  133. }
  134. }
  135. - (void)removeAllFrames {
  136. @synchronized (self) {
  137. [self.frameBuffer removeAllObjects];
  138. }
  139. }
  140. @end