AsyncDisplayKit学习

前言

AsyncDisplayKit是14年开源出来的库了,每一个库开发出来肯定都是为了解决某些问题的,所以藉着有任务在身就研究一下这个UI库的原理作为原始积累吧。

60fps

To keep its user interface smooth and responsive, your app should render at 60 frames per second — the gold standard on iOS. This means the main thread has one-sixtieth of a second to push each frame. That’s 16 milliseconds to execute all layout and drawing code!

以上摘自AsyncDisplayKit的Github介绍,在开始介绍AsyncDisplayKit之前,首先要介绍一下这个数字及单位以作科普。fps即feet pre second, 在iOS应用界面流畅体验当中,业界通常会用用户在操作时界面的帧数达到60帧每秒来作为一个应用流畅的基准。
在开发中我们可以通过XCode自带的Instruments中的Core Animation来查看及调试我们的界面的帧数,找到帧数最低的从而进行优化。

简介

AsyncDisplayKit能让你通过将图像解码、布局以及渲染操作放在后台线程,从而带来超级响应的用户界面,也就是说不再会因界面卡顿而阻断用户交互。简而言之,就是可以让我即使开发出了很复杂的界面,只要我们使用这个库,就都可以达到60fps,做到最好的用户体验。

为什么这个库出现了?

我们知道,iOS的View渲染是交给主线程去做渲染的,并且UIView是非线程安全的,不能做异步渲染。当应用启动,页面初始化时,所有我们写的UI都会在主线程进行加载,如果UI过于复杂,就会造成卡顿。
所以,Facebook的Paper团队给我们带来这个lib,AsyncDisplayKit。

原理

AsyncDisplayKit’s basic unit is the node. An ASDisplayNode is an abstraction over UIView, which in turn is an abstraction over CALayer.

AsyncDisplayKit的最小单元是其自定义出来的ASDisplayNode。ASDisplayNode是UIView之上的抽象层,同时也是CALayer的抽象层。和只能被用在主线程的视图不同,nodes是线程安全的:你能并行的实例化并设置整个node层级,并且在后台线程里运行。假设我们要用这个库渲染一个UIImage,AsyncDisplayKit会让你把image的解码,text sizing和渲染,以及其他的在费时的UI从左放置在了其他线程。(在使用view的时候,这些操作都只能放置在UI线程)

举个栗子,传统的UIImage:

_imageView = [[UIImageView alloc] init];
_imageView.image = [UIImage imageNamed:@"hello"];
_imageView.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageView];

而AsyncDisplayKit的UIImage:

_imageNode = [[ASImageNode alloc] init];
_imageNode.backgroundColor = [UIColor lightGrayColor];
_imageNode.image = [UIImage imageNamed:@"hello"];
_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageNode.view];

ASImageNode为例

我们来看看ASImageNode的继承关系图:
这里写图片描述

可以看到,一切的起点都是ASDisplayNode,其实跟UIView->UIControl->UIImage的继承结构很相像。

阅读了下源码ASImageNode.mm 后发现,当 ASImageNode 初始化执行到+(void)initialize时,会先开启一个全局断言函数来监听UI渲染过程的error。

+ (void)initialize
{
  [super initialize];

  if (self != [ASImageNode class]) {
    // Prevent custom drawing in subclasses
    ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASImageNode class], self, @selector(displayWithParameters:isCancelled:)), @"Subclass %@ must not override displayWithParameters:isCancelled: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASImageNode class]));
  }
}

当执行到init时,除了定义一些关于UIImage的Layer的初始属性外,还会定义一个UI渲染的Runloop模式

  _animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode;

当给node设置Image时,会触发:

- (void)setImage:(UIImage *)image
{
  ASDN::MutexLocker l(__instanceLock__);
  if (!ASObjectIsEqual(_image, image)) {
    _image = image;

    [self invalidateCalculatedLayout];
    if (image) {
      [self setNeedsDisplay];

      if ([ASImageNode shouldShowImageScalingOverlay] && _debugLabelNode == nil) {
        ASPerformBlockOnMainThread(^{
          _debugLabelNode = [[ASTextNode alloc] init];
          _debugLabelNode.layerBacked = YES;
          [self addSubnode:_debugLabelNode];
        });
      }
    } else {
      self.contents = nil;
    }
  }
}

在配置好image后,ASImageNode就会触发- (UIImage *)displayWithParameters:(id<NSObject> *)parameter isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
这个function,我们可以看到,在对image进行一系列的配置后,会讲image封装成一个ASImageNodeContentsKey,然后再打包成一个ASWeakMapEntry,从而使我们的图片以cache的形式保存,当进行图片渲染时,该库就会从异步吧cache取出并渲染到界面上,从而优化了主线程,达到了提高用户体验的目的。

总结

综上,AsyncDisplayKit将一些UI绘制可以移到工作线程的工作剥离主线程,并使用iOS的线程技巧来做同步,使用帧数达到60fps,而且在我看来这些技术技巧其实是通用的,完全可以用于iOS甚至Android等其他客户端的编程当中。

当然,AsyncDisplayKit大量的采用线程,也带来了一些接口API在线程同步中不好使用的问题,所以,个人觉得在开发过程如果UI绘制占用的内存并不是特别大的话,可以采用原生的开发模式,毕竟这只是一个给我们优化界面的方式,我们需要考虑维护的成本。 暂写到这,以后有想到什么再续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值