AsyncDisplayKit

作者: liboxiang | 来源:发表于2019-01-08 14:56 被阅读55次

AsyncDisplayKit是Facebook开源的一套用于iOS界面流畅的异步绘制UI的框架

Runloop

https://juejin.im/post/5b59770c5188251b166f0638
通过监听UI主线程runloop的BeforeWaiting和Exit事件,将子线程中的修改合并到UI主线程


封装的group中通过int型数据表示group的enter、leave和开启的线程数
void ASAsyncTransactionQueue::GroupImpl::enter()
{
  std::lock_guard<std::mutex> l(_queue._mutex);//锁
  ++_pendingOperations;
    NSLog(@"add _pendingOperations = %@",[NSThread currentThread]);
}

void ASAsyncTransactionQueue::GroupImpl::leave()
{
  std::lock_guard<std::mutex> l(_queue._mutex);//锁
  --_pendingOperations;
...
}
++entry._threadCount;//开启的线程数
    
    dispatch_async(queue, ^{
      std::unique_lock<std::mutex> lock(q._mutex);
      
      // go until there are no more pending operations
      while (!entry._operationQueue.empty()) {
        Operation operation = entry.popNextOperation(respectPriority);
        lock.unlock();
        if (operation._block) {
          ASProfilingSignpostStart(3, operation._block);
          operation._block();
          ASProfilingSignpostEnd(3, operation._block);
        }
        operation._group->leave();
        operation._block = nil; // the block must be freed while mutex is unlocked
        lock.lock();
      }
      --entry._threadCount;
      
      if (entry._threadCount == 0) {
        NSCAssert(entry._operationQueue.empty() || entry._operationPriorityMap.empty(), @"No working threads but operations are still scheduled"); // this shouldn't happen
        q._entries.erase(queue);
      }
    });

runloop observer优先级在CA动画之后


D614AA0D-D3CD-4744-A681-8AC98B2650BB.png
static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
  ASDisplayNodeCAssertMainThread();
  _ASAsyncTransactionGroup *group = (__bridge _ASAsyncTransactionGroup *)info;
  [group commit];//将需要主线程处理的操作提交到主线程
}

并发操作及并发数控制

void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)
{
  ASAsyncTransactionQueue &q = _queue;
  std::lock_guard<std::mutex> l(q._mutex);
  
  DispatchEntry &entry = q._entries[queue];
  
  Operation operation;
  operation._block = block;
  operation._group = this;
  operation._priority = priority;
  entry.pushOperation(operation);
  
  ++_pendingOperations; // enter group
#if ASDISPLAYNODE_DELAY_DISPLAY
  NSUInteger maxThreads = 1;
#else
    //当前激活的CPU总数
  NSUInteger maxThreads = [NSProcessInfo processInfo].activeProcessorCount * 2;

  // Bit questionable maybe - we can give main thread more CPU time during tracking;
  if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode])
    --maxThreads;
#endif
    //控制并发数量
  if (entry._threadCount < maxThreads) { // we need to spawn another thread

    // first thread will take operations in queue order (regardless of priority), other threads will respect priority
    bool respectPriority = entry._threadCount > 0;
    ++entry._threadCount;
    dispatch_async(queue, ^{
      std::unique_lock<std::mutex> lock(q._mutex);
      
      // go until there are no more pending operations
      while (!entry._operationQueue.empty()) {
        Operation operation = entry.popNextOperation(respectPriority);
        lock.unlock();
        if (operation._block) {
          ASProfilingSignpostStart(3, operation._block);
          operation._block();
          ASProfilingSignpostEnd(3, operation._block);
        }
        operation._group->leave();
        operation._block = nil; // the block must be freed while mutex is unlocked
        lock.lock();
      }
      --entry._threadCount;
      if (entry._threadCount == 0) {
        NSCAssert(entry._operationQueue.empty() || entry._operationPriorityMap.empty(), @"No working threads but operations are still scheduled"); // this shouldn't happen
        q._entries.erase(queue);
      }
    });
  }
}

如果操作在实际执行之前被取消,则不再执行

_group->schedule(priority, queue, ^{
    @autoreleasepool {
      if (self.state != ASAsyncTransactionStateCanceled) {
        operation.value = block();
      }
    }
  });

绘制

if (shouldBeginRasterizing) {
    // Collect displayBlocks for all descendants.
    NSMutableArray *displayBlocks = [NSMutableArray array];
    [self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
    CHECK_CANCELLED_AND_RETURN_NIL();
    
    // If [UIColor clearColor] or another semitransparent background color is used, include alpha channel when rasterizing.
    // Unlike CALayer drawing, we include the backgroundColor as a base during rasterization.
    opaque = opaque && CGColorGetAlpha(self.backgroundColor.CGColor) == 1.0f;

    displayBlock = ^id{
      CHECK_CANCELLED_AND_RETURN_NIL();
      
      UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);

      for (dispatch_block_t block in displayBlocks) {
        CHECK_CANCELLED_AND_RETURN_NIL(UIGraphicsEndImageContext());
        block();
      }
      
      UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();

      ASDN_DELAY_FOR_DISPLAY();
      return image;
    };
  } else {
    displayBlock = ^id{
      CHECK_CANCELLED_AND_RETURN_NIL();

      if (shouldCreateGraphicsContext) {
        UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
        CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
      }

      CGContextRef currentContext = UIGraphicsGetCurrentContext();
      UIImage *image = nil;
        
      ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = nil;
      ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = nil;
      if (currentContext) {
        __instanceLock__.lock();
        willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext;
        didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
        __instanceLock__.unlock();
      }
        

      // For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
      // _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
      if (willDisplayNodeContentWithRenderingContext != nil) {
        willDisplayNodeContentWithRenderingContext(currentContext);
      }
      
      // Decide if we use a class or instance method to draw or display.
      id object = usesInstanceMethodDisplay ? self : [self class];
      
      if (usesImageDisplay) {                                   // If we are using a display method, we'll get an image back directly.
        image = [object displayWithParameters:drawParameters
                                  isCancelled:isCancelledBlock];
      } else if (usesDrawRect) {                                // If we're using a draw method, this will operate on the currentContext.
        [object drawRect:bounds withParameters:drawParameters
             isCancelled:isCancelledBlock isRasterizing:rasterizing];
      }
      
      if (didDisplayNodeContentWithRenderingContext != nil) {
        didDisplayNodeContentWithRenderingContext(currentContext);
      }
      
      if (shouldCreateGraphicsContext) {
        CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); );
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
      }

      ASDN_DELAY_FOR_DISPLAY();
      return image;
    };
  }

图片解码

当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。

CoreGraphic 方法通常都是线程安全的

文本渲染

相关文章

网友评论

    本文标题:AsyncDisplayKit

    本文链接:https://www.haomeiwen.com/subject/ubamrqtx.html