YYAsyncLayer.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. //
  2. // YYAsyncLayer.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/4/11.
  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 "YYAsyncLayer.h"
  12. #import "YYSentinel.h"
  13. #if __has_include("YYDispatchQueuePool.h")
  14. #import "YYDispatchQueuePool.h"
  15. #else
  16. #import <libkern/OSAtomic.h>
  17. #endif
  18. /// Global display queue, used for content rendering.
  19. static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
  20. #ifdef YYDispatchQueuePool_h
  21. return YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
  22. #else
  23. #define MAX_QUEUE_COUNT 16
  24. static int queueCount;
  25. static dispatch_queue_t queues[MAX_QUEUE_COUNT];
  26. static dispatch_once_t onceToken;
  27. static int32_t counter = 0;
  28. dispatch_once(&onceToken, ^{
  29. queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
  30. queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
  31. if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
  32. for (NSUInteger i = 0; i < queueCount; i++) {
  33. dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
  34. queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
  35. }
  36. } else {
  37. for (NSUInteger i = 0; i < queueCount; i++) {
  38. queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
  39. dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
  40. }
  41. }
  42. });
  43. int32_t cur = OSAtomicIncrement32(&counter);
  44. if (cur < 0) cur = -cur;
  45. return queues[(cur) % queueCount];
  46. #undef MAX_QUEUE_COUNT
  47. #endif
  48. }
  49. static dispatch_queue_t YYAsyncLayerGetReleaseQueue() {
  50. #ifdef YYDispatchQueuePool_h
  51. return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
  52. #else
  53. return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
  54. #endif
  55. }
  56. @implementation YYAsyncLayerDisplayTask
  57. @end
  58. @implementation YYAsyncLayer {
  59. YYSentinel *_sentinel;
  60. }
  61. #pragma mark - Override
  62. + (id)defaultValueForKey:(NSString *)key {
  63. if ([key isEqualToString:@"displaysAsynchronously"]) {
  64. return @(YES);
  65. } else {
  66. return [super defaultValueForKey:key];
  67. }
  68. }
  69. - (instancetype)init {
  70. self = [super init];
  71. static CGFloat scale; //global
  72. static dispatch_once_t onceToken;
  73. dispatch_once(&onceToken, ^{
  74. scale = [UIScreen mainScreen].scale;
  75. });
  76. self.contentsScale = scale;
  77. _sentinel = [YYSentinel new];
  78. _displaysAsynchronously = YES;
  79. return self;
  80. }
  81. - (void)dealloc {
  82. [_sentinel increase];
  83. }
  84. - (void)setNeedsDisplay {
  85. [self _cancelAsyncDisplay];
  86. [super setNeedsDisplay];
  87. }
  88. - (void)display {
  89. super.contents = super.contents;
  90. [self _displayAsync:_displaysAsynchronously];
  91. }
  92. #pragma mark - Private
  93. - (void)_displayAsync:(BOOL)async {
  94. __strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
  95. YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
  96. if (!task.display) {
  97. if (task.willDisplay) task.willDisplay(self);
  98. self.contents = nil;
  99. if (task.didDisplay) task.didDisplay(self, YES);
  100. return;
  101. }
  102. if (async) {
  103. if (task.willDisplay) task.willDisplay(self);
  104. YYSentinel *sentinel = _sentinel;
  105. int32_t value = sentinel.value;
  106. BOOL (^isCancelled)() = ^BOOL() {
  107. return value != sentinel.value;
  108. };
  109. CGSize size = self.bounds.size;
  110. BOOL opaque = self.opaque;
  111. CGFloat scale = self.contentsScale;
  112. CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
  113. if (size.width < 1 || size.height < 1) {
  114. CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
  115. self.contents = nil;
  116. if (image) {
  117. dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
  118. CFRelease(image);
  119. });
  120. }
  121. if (task.didDisplay) task.didDisplay(self, YES);
  122. CGColorRelease(backgroundColor);
  123. return;
  124. }
  125. dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
  126. if (isCancelled()) {
  127. CGColorRelease(backgroundColor);
  128. return;
  129. }
  130. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  131. CGContextRef context = UIGraphicsGetCurrentContext();
  132. if (opaque && context) {
  133. CGContextSaveGState(context); {
  134. if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
  135. CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  136. CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
  137. CGContextFillPath(context);
  138. }
  139. if (backgroundColor) {
  140. CGContextSetFillColorWithColor(context, backgroundColor);
  141. CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
  142. CGContextFillPath(context);
  143. }
  144. } CGContextRestoreGState(context);
  145. CGColorRelease(backgroundColor);
  146. }
  147. task.display(context, size, isCancelled);
  148. if (isCancelled()) {
  149. UIGraphicsEndImageContext();
  150. dispatch_async(dispatch_get_main_queue(), ^{
  151. if (task.didDisplay) task.didDisplay(self, NO);
  152. });
  153. return;
  154. }
  155. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  156. UIGraphicsEndImageContext();
  157. if (isCancelled()) {
  158. dispatch_async(dispatch_get_main_queue(), ^{
  159. if (task.didDisplay) task.didDisplay(self, NO);
  160. });
  161. return;
  162. }
  163. dispatch_async(dispatch_get_main_queue(), ^{
  164. if (isCancelled()) {
  165. if (task.didDisplay) task.didDisplay(self, NO);
  166. } else {
  167. self.contents = (__bridge id)(image.CGImage);
  168. if (task.didDisplay) task.didDisplay(self, YES);
  169. }
  170. });
  171. });
  172. } else {
  173. [_sentinel increase];
  174. if (task.willDisplay) task.willDisplay(self);
  175. UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
  176. CGContextRef context = UIGraphicsGetCurrentContext();
  177. if (self.opaque && context) {
  178. CGSize size = self.bounds.size;
  179. size.width *= self.contentsScale;
  180. size.height *= self.contentsScale;
  181. CGContextSaveGState(context); {
  182. if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {
  183. CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  184. CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
  185. CGContextFillPath(context);
  186. }
  187. if (self.backgroundColor) {
  188. CGContextSetFillColorWithColor(context, self.backgroundColor);
  189. CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
  190. CGContextFillPath(context);
  191. }
  192. } CGContextRestoreGState(context);
  193. }
  194. task.display(context, self.bounds.size, ^{return NO;});
  195. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  196. UIGraphicsEndImageContext();
  197. self.contents = (__bridge id)(image.CGImage);
  198. if (task.didDisplay) task.didDisplay(self, YES);
  199. }
  200. }
  201. - (void)_cancelAsyncDisplay {
  202. [_sentinel increase];
  203. }
  204. @end