AsyncDisplayKit使用详解

AsyncDisplayKit的核心组件包括:

· ASDisplayNode UIView对应一个子类,用来自定node

·  ASControlNode. 类似于UIControl —用来扩展生成buttons

·  ASImageNode. 类似于UIImageView —异步的图像解码.

·  ASTextNode. 类似于UITextView —基于TextKit构建,支持富文本的全部特性。

·  ASTableView. UITableView子类,用于支持node


1.基本单元是nodeASDisplayNodeuiview的抽象,也是CALayer的抽象。node不像view只能用在主线程,node线程安全的。你可以在后台线程同时初始化和配置他们的整个继承链。 



2.为了保持界面平滑和可相应,app应该保持每秒60帧的渲染,意味着主线程只有1/60秒来推每一帧。就是用16毫秒来执行所有布局和绘制代码。由于一些系统负载,你的代码经常只有10毫秒来运行,超过10毫秒就会掉帧。 



3. AsyncDisplayKit让你把图片解码,文字缩放和渲染等昂贵的ui操作从主线程中去除。 


优化

层处理 Layer-Backing

有些时候,大幅度使用Layer而不是使用views,可以提高你的app的性能,但是手动的把基于view开发的界面代码改为基于layer的界面代码,非常的费劲,如果有时候因为要开启触摸或者view特定的功能的时候,你可能要功能回退

当你使用ASDK的node的时候,如果你打算把所有的view转换成layer,只需要一行代码

rootNode.layerBacked = YES;

如果你想回退,也只需要删除这一行,我们建议不需要触摸处理的所有视图都开启

同步并发

敬请期待……

子树光栅化

预压缩,扁平化整个视图层级到一个图层,也可以提高性能,node也可以帮你做这件事

rootNode.shouldRasterizeDescendants = YES;

你的整个node层级都会渲染在一个layer下


AsyncDisplayKit的组件

节点容器

在容器中使用Nodes

AsyncDisplayKit有以下几种 Container

ASDK Node Container UIKit Equivalent
ASCollectionNode 代替UIKit的 UICollectionView
ASPagerNode 代替UIKit的 UIPageViewController
ASTableNode 代替UIKit的 UITableView
ASViewController 代替UIKit的 UIViewController
ASNavigationControllerv 代替UIKit的 UINavigationController,遵循ASVisibility协议
ASTabBarController 代替UIKit的 UITabBarController,遵循ASVisibility协议

Node Container的优势

Node Container可以自动地管理node的智能预加载,它会保证node的的布局计算、数据获取、解码和渲染会以异步的方式进行。

Node的子类

AsyncDisplayKit提供了以下几种node

相对UIKit的组件来说 node 最大的改进就是将所有布局和显示都放在子线程,因此主线程就能够即时地响应各种事件。

ASDK Node UIKit Equivalent
ASDisplayNode 代替UIKit的 UIView ,其他的node都继承于它
ASCellNode 代替UIKit的 UITableViewCell 和 UICollectionViewCell,用在 ASTableNode , ASCollectionNode 和 ASPagerNode
ASScrollNode 代替UIKit的 UIScrollView,这个 node 用在自定义滚动区域上非常有用
ASEditableTextNode 代替UIKit的 UITextView
ASTextNode 代替UIKit的 UILabel
ASImageNode 代替UIKit的 UIImage
ASNetworkImageNode
ASMultiplexImageNode
ASVideoNode 代替UIKit的 AVPlayerLayer
ASVideoPlayerNode 代替UIKit的 UIMoviePlayer
SControlNode 代替UIKit的 UIControl
ASButtonNode 代替UIKit的 UIButton
ASMapNode 代替UIKit的 MKMapView

AsyncDisplayKit 中的 node 相比UIKit中的组件更先进而且更方便。比如 ASNetworkImageNode 可以自动地加载和进行内存管理,而且还支持逐步加载Jpeg和动态gif图片。

node 的层级结构如下



上图中蓝色高亮的node是对UIKit元素的封装而成。比如 ASScrollNode 封装自 UIScrollView , ASCollectionNode 封装自 UICollectionView ,ASMapNode 在实时地图模式中就是 UIMapView 封装。

基本重载方法

创建子类时,子类是 ASViewController 还是 ASDisplayNode 有很大的区别。这感觉上是显而易见的,但是有的区别是很微妙的,所有也需要特别注意。

ASDisplayNode

  • ASDisplayNode内部持有了UIView,将view封装起来
  • ASDisplayNode充当UIView的delegate,原本view产生的各种事件,由于已经不直接操作UIView,因此会delegate通知node进行处理
  • ASDiplayNode可以通过.view直接访问UIView
  • ASDiplayNode管理着UIView,接管了UIView的一些处理操作
  • ASDiplayNode通过对异步处理的改造,让使用者可以在安全的在线程进行操作

ASDisplayNode

如果你使用的 node 组件类型于 UIView 的子控件,就必须注意下面几条准则以确保你充分发挥了这个框架的潜力, node 也能很好的显示。

-init

当使用 nodeBlock 的时候这个方法是在子线程进行的。但是,如何 -init 没有运行完,其他方法就无法运行,所以这个方法永远不要上锁。

最重要的是你的 init 方法一定要能在任何队列中调用。尤其是不要在初始化的时候初始化任何 UIKit 控件、点击 node 的 view 或者 layer 或者添加任何手势。这些事情应该放在 -didLoad 中。

-didLoad

这个方法在概念上类似于 UIViewController 的 -viewDidLoad 方法,它代表着后台的 view 已经被加载完成了。要确保它是在主线程中执行的,在这个方法中很适合处理 UIKit 的各种操作(比如添加手势,点击 view 或者 layer,初始化 UIKit 组件)。

-layout

在这个方法里调用 super 之后,布局规则对象会把所有的子节点都计算并且定位好,所以这个时间点是你手动进行布局所有子view的时机。或许更有用的是,有时候你想手动布局,但并不太容易创建一个布局规则对象,或者有时候你不想等所有子节点布局完毕,而只是很简单的手动设置frame,如果是这样的话,就在这个方法里写。

layoutSpecThatFits

这个方法就是用来建立布局规则对象,产生 node 大小以及所有子 node 大小的地方,你创建的布局规则对象一直持续到这个方法返回的时间点,经过了这个时间点后,它就不可变了。尤其重要要记住的一点事,千万不要缓存布局规则对象,当你以后需要他的时候,请重新创建。

ASViewController

ASViewController 是 UIViewController 的子类,包含了管理 node 的特性。因此它的所有方法一定要在主线程中使用。

-init

这个方法只调用一次,是在 ASViewController 的生命周期的最开始的时候调用。在它初始化的过程中,不要使用 self.view 或者 self.node.view ,它会强制 view 被提前创建。这些事情应该在 viewDidLoad 中完成。

ASViewController 的初始化方法是 initWithNode: ,代码如下所示。一个 ASViewController 管理节点就像 UIViewController 管理 view 一样,但是初始化的过程有小小的差异。

 - (instancetype)init
 {
   _pagerNode = [[ASPagerNode alloc] init];
   self = [super initWithNode:_pagerNode];

   // setup any instance variables or properties here
   if (self) {
     _pagerNode.dataSource = self;
     _pagerNode.delegate = self;
   }
   return self;
 }
-loadView

建议不要使用这个方法,因为它相对于 viewDidLoad 来说没有明显的优势反而有很多不足的地方。但如果你不去设置 self.view 的属性就没什么问题。调用 [super loadView]它就会执行 node.view 。

-viewDidLoad

它会在 ASViewController 的生命周期的最开始调用,仅次于 -loadView 。这是你使用节点的 view 最早的时机。在这个方法里适合放只执行一次并且要使用 view/layer 的代码,比如加手势。

布局的代码千万不要放在这个方法里,因为界面发生改变也不会再调用这个方法重新布局。

-viewWillLayoutSubviews

这个方法调用的时机和节点 -layout 方法调用的时机一样,它可以被执行很多次。只要它的节点被改变(比如旋转,分屏,显示键盘)或者继承()都会立即被调用。

- (instancetype)init

{

    if (self = [superinitWithNode:[ASDisplayNodenew]]) {

        [selfaddTableNode];

    }

    returnself;

}


- (void)addTableNode

{

    _tableNode = [[ASTableNodealloc]initWithStyle:UITableViewStyleGrouped];

    _tableNode.backgroundColor = [UIColorwhiteColor];

    _tableNode.delegate =self;

    _tableNode.dataSource =self;

    [self.nodeaddSubnode:_tableNode];

}


/


- (void)viewDidLoad {

    [superviewDidLoad];

    // Do any additional setup after loading the view.

    // 在一个控制器中读取数据,作为_tableNode的数据源

    [selfloadData];

}


 // 读取数据的方法

- (void)loadData

{

    [selfloadMoreDataWithContext:nil];

}

 // 读取数据的方法,context作为数据的上下文

- (void)loadMoreDataWithContext:(ASBatchContext *)context

{

    if (context) {

        [context beginBatchFetching];

    }else {

        _curIndexPage =1;

        _haveMore =YES;

        [LPLoadingViewshowLoadingInView:self.view];

    }

    

    LPHttpRequest *hotZoneRequest = [LPGameZoneOperation requestHotZoneWithPageIndex:_curIndexPage];

    [hotZoneRequest loadWithSuccessBlock:^(LPHttpRequest *request) {

        LPHotZoneModel *hotZoneModel = request.responseObject.data;

        NSArray *threadList = hotZoneModel.threadList;

        if (context) {

            if (threadList.count >0) {

                NSMutableArray *indexPaths = [NSMutableArray array];

                for (NSInteger row =_threadList.count; row< _threadList.count+threadList.count; ++row) {

                    [indexPaths addObject:[NSIndexPathindexPathForRow:rowinSection:0]];

                }

                _threadList = [_threadList arrayByAddingObjectsFromArray:threadList];

                [_tableNode.view insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];

                _curIndexPage++;

                _haveMore =YES;

            }else {

                _haveMore =NO;

            }

            [context completeBatchFetching:YES];

        }else {

            _focusList = hotZoneModel.focusList;

            _forumList = hotZoneModel.forumList;

            _threadList = hotZoneModel.threadList;

            [selfaddCycleScrollView];

            [_tableNode.viewreloadData];

            ++_curIndexPage;

            _haveMore =YES;

            [LPLoadingView hideLoadingForView:self.view];

        }

    } failureBlock:^(id<TYRequestProtocol> request,NSError *error) {

        [LPLoadingView hideLoadingForView:self.view];

        if (context) {

            [context completeBatchFetching:YES];

        }else {

            __weaktypeof(self) weakSelf =self;

            [LPLoadFailedView showLoadFailedInView:self.view retryHandle:^{

                [weakSelf loadData];

            }];

        }

    }];

    

}


用批抓取进行无限滚动

在大部分 App 中,服务器上拥有的数据远远多于在表格中一次能显示下的 cell 的行数。也就是说,每个 App 都应采用某些机制,来保证用户浏览到当前数据集结尾时,随时从服务器上拉取新的数据。

过去,这只能通过 Scroll View 的委托方法 -scrollViewDidScroll: 来进行手动处理。在 ASDK 中,有一种更明确的解决方式。

你可以预先指定多少页,才需要加载新的数据。 
首先,取消被注释的助手方法。找到 AnimalTableController.m 最后,取消 Helpers 类别中的两个方法注释。-retrieveNextPageWithCompletion: 方法可以看出是网络调用,而 -insertNewRowsInTableNode: 方法只是一个一般方法,将新的数据添加到表格中。

首先,在 -viewDidLoad: 方法中加入一句:

self.tableNode.view.leadingScreensForBatching = 1.0;  // 默认值是 2.0
    
    
  • 1
  • 1

将 leadingScreensForBatching 设置为 1.0 表示你当用户滚动还剩 1 个全屏就到达数据末尾时,就开始抓取新的一批数据。

然后,在 Delegate 类别中加入这个方法:

- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode {
  return YES;
}
    
    
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

这个方法告诉表格,在这次批抓取之后是否还可以进行新的批抓取。如果你知道 API 上的数据什么时候结束,返回 NO,表示不用进行新的批抓取了。

因为你想让表格无限滚动,所以返回 YES,表示永远能够进行新的批抓取。 
然后,继续加入:

- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context {
  //1
  [self retrieveNextPageWithCompletion:^(NSArray *animals) {
    //2
    [self insertNewRowsInTableNode:animals];

    //3
    [context completeBatchFetching:YES];
  }];
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这个方法在用户即将滚动到表格末尾并且 -shouldBatchFetchForTableNode: 方法返回 YES 时调用。 
代码解释如下:

  • 首先,请求一次新批抓取,以备显示。通常这会从 API 返回一个对象数组。
  • 在完成块中,用新数据刷新表格。
  • 最后,调用 -completeBatchFetching: ,传递一个 YES 表示你已经完成。在本次批抓取完成之前,不会进行新的批抓取。

@property (nonatomic,strong)ASNetworkImageNode *imageNode;

ASNetworkImageNode

作用同等于 UIImageView,如果使用网络图片请使用此类,ASDK 用的是第三方的图片加载库PINRemoteImageASNetworkImageNode 其实并不支持 gif,如果需要显示 gif 推荐使用FLAnimatedImage

ASDK的容器

每一个Node节点是可以直接通过UIView的addSubnode方法添加到原本的UIView之上,但是官方在文档里并不推荐这么做,我前一篇的官方文档翻译中有写

当在项目中替换使用AsyncDisplayKit的时候,一个经常犯的错误就是把一个Node节点直接添加到一个现有的view视图层次结构里。这样做会导致你的节点在渲染的时候会闪烁一下

相反,你应该你应该把nodes节点,当做一个子节点添加到一个容器类里。这些容器类负责告诉所包含的节点,他们现在都是什么状态,以便于尽可能有效的加载数据与渲染。你可以把这些类当做UIKit和ASDK的整合点

借助nodes容器可以更好的对容器内子nodes进行管理和渲染控制,这是官方推荐使用的,因此我们引出了节点容器的概念

ASViewController

ASViewController就是这样的一种节点容器,它并非继承自ASDisplayNode,而是直接继承自UIViewController,就好像每一个UIViewController一定要有一个self.view一样,ASViewController必须由一个ASDisplayNode进行初始化。

ASTableNode

ASCollectionNode

这两个节点,本身就是一个ASDisplayNode节点,但它同时也有节点容器的作用

不仅如此,Table和Collection都是用于滚动并且批量展示数据的,ASDK还特意为此封装了一套复杂的处理----滚动情况下的数据异步加载过程。

因此他内部有两个很特殊的控制器

  • ASRangeController 用于智能判断滚动范围和滚动方向,提起对即将滚入屏幕的区域进行预处理控制,包括预处理数据加载,和预处理渲染
  • ASDataController 专门用于数据相关的加载控制,在RangeController的指挥下,对指定区域内的数据进行加载。

- (instancetype)initWithItem:(LPZoneDiscuzItem *)item

{

    if (self = [superinit]) {

        _item = item;

        

        self.backgroundColor = [UIColorwhiteColor];

        

        [selfaddImageNode];

        

        [selfaddTitleNode];

        

        [selfaddDetailTextNode];

    }

    returnself;

}

在ASDK 2.0版本中,.preferredFrameSize被改为widthheight等其他一系列属性,详见最新版头文件

对象指定.flexShrink,.flexGrow则是表示,该对象所处在的LayoutSpec位置不足/多余时,其是否会自动缩小/扩充。

ASDK 2.0之前,.flexShrink,.flexGrow默认为NO,2.0之后,将默认打开


- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize

{

    _imageNode.preferredFrameSize =CGSizeMake(54,54);

    _titleNode.flexShrink =YES;

    _detailTextNode.flexShrink =YES;

    

    ASStackLayoutSpec *verStackLayout = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVerticalspacing:10justifyContent:ASStackLayoutJustifyContentStartalignItems:ASStackLayoutAlignItemsStretchchildren:@[_titleNode,_detailTextNode]];

    

    ASStackLayoutSpec *horStackLayout = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontalspacing:10justifyContent:ASStackLayoutJustifyContentStartalignItems:ASStackLayoutAlignItemsCenterchildren:@[_imageNode,verStackLayout]];

    

    ASInsetLayoutSpec *insetLayout = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10,13, 10,10) child:horStackLayout];

    return insetLayout;

}

  1. 最后通过一个 ASInsetLayoutSpec 设置一个边距。


ASStackLayoutSpec

可以说这是最常用的类,而且相对于其他类来说在功能上是最接近于 AutoLayout 的。 之所以称之为盒子布局是因为它和 CSS 中 Flexbox 很相似,不清楚 Flexbox 的可以先看下阮一峰的这篇博客

先看一个例子:

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
    self.childNodeA.style.preferredSize = CGSizeMake(100, 100);
    self.childNodeB.style.preferredSize = CGSizeMake(200, 200);
    ASStackLayoutSpec *stackLayout = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
                                                                             spacing:12
                                                                      justifyContent:ASStackLayoutJustifyContentStart
                                                                          alignItems:ASStackLayoutAlignItemsStart
                                                                            children:@[self.childNodeA, self.childNodeB]];
    return stackLayout;
}

简单的说明下各个参数的作用:

  1. direction:主轴的方向,有两个可选值:
    • 纵向:ASStackLayoutDirectionVertical
    • 横向:ASStackLayoutDirectionHorizontal
  2. spacing: 主轴上视图排列的间距,比如有四个视图,那么它们之间的存在三个间距值都应该是spacing
  3. justifyContent: 主轴上的排列方式,有五个可选值:
    • ASStackLayoutJustifyContentStart 从前往后排列
    • ASStackLayoutJustifyContentCenter 居中排列
    • ASStackLayoutJustifyContentEnd 从后往前排列
    • ASStackLayoutJustifyContentSpaceBetween 间隔排列,两端无间隔
    • ASStackLayoutJustifyContentSpaceAround 间隔排列,两端有间隔
  4. alignItems: 交叉轴上的排列方式,有五个可选值:
    • ASStackLayoutAlignItemsStart 从前往后排列
    • ASStackLayoutAlignItemsEnd 从后往前排列
    • ASStackLayoutAlignItemsCenter 居中排列
    • ASStackLayoutAlignItemsStretch 拉伸排列
    • ASStackLayoutAlignItemsBaselineFirst 以第一个文字元素基线排列(主轴是横向才可用)
    • ASStackLayoutAlignItemsBaselineLast 以最后一个文字元素基线排列(主轴是横向才可用)
  5. children: 包含的视图。数组内元素顺序同样代表着布局时排列的顺序,所以需要注意

主轴的方向设置尤为重要,如果主轴设置的是 ASStackLayoutDirectionVertical, 那么 justifyContent 各个参数的意义就是:

  • ASStackLayoutJustifyContentStart 从上往下排列
  • ASStackLayoutJustifyContentCenter 居中排列
  • ASStackLayoutJustifyContentEnd 从下往上排列
  • ASStackLayoutJustifyContentSpaceBetween 间隔排列,两端无间隔
  • ASStackLayoutJustifyContentSpaceAround 间隔排列,两端有间隔

alignItems 就是:

  • ASStackLayoutAlignItemsStart 从左往右排列
  • ASStackLayoutAlignItemsEnd 从右往左排列
  • ASStackLayoutAlignItemsCenter 居中排列
  • ASStackLayoutAlignItemsStretch 拉伸排列
  • ASStackLayoutAlignItemsBaselineFirst 无效
  • ASStackLayoutAlignItemsBaselineLast 无效

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值