ASDK技术栈

ASDK的核心概念(三)

2018-03-26  本文已影响135人  iYeso

一: 智能预加载
二: Node Containers
三: Node Subclasses
四: Subclassing
五: FAQ

一: 预加载

虽然Texture的异步和同时渲染和测量的能力使其非常强大,但对于纹理来说另一个非常重要的层次是智能预加载的做法。

正如“入门指南”中指出的那样,在其中一个节点容器的上下文之外使用节点是不对的。这是由于所有节点都具有其当前接口状态的概念。

此接口状态属性由ASRangeController不断更新,所有容器都在内部创建和维护。

在容器外部使用的节点不会由任何范围控制器更新其状态。这有时会导致闪烁,因为在意识到它们已经在屏幕上而没有任何警告的情况下呈现节点。

1.1: Interface State Ranges

当节点添加到滚动或分页界面时,它们通常位于以下范围之一中。这意味着随着滚动视图的滚动,它们的界面状态将在它们移动时更新。

接口状态范围

节点将处于以下范围之一中:

接口状态 描述
Preload 远离可见的最远距离。这是从外部来源收集内容的地方,无论是API还是本地磁盘。
Display 在这里,显示任务,如文本光栅化和图像解码。
Visible 该节点在屏幕上至少有一个像素。
1.2: ASRangeTuningParameters

每个这些范围的大小以“screenfuls”来衡量。尽管默认大小在很多用例中都能很好地工作,但可以通过在滚动节点上为范围类型设置调整参数来轻松调整它们。


image

在上述滚动收藏的可视化中,用户正在向下滚动。如您所见,领先方向上的范围大小比用户远离的内容(拖尾方向)大得多。 如果用户要改变方向,则前端和后端将动态交换以保持内存使用最佳。 这使您可以担心定义前导和尾随尺寸,而无需担心对用户变化的滚动方向作出反应。

智能预加载也可以在多个方面工作。

1.3: Interface State Callbacks

当用户滚动时,节点在范围内移动并通过加载数据,渲染等方式做出适当反应。您自己的节点子类可以通过实现相应的回调方法轻松访问此机制。

二: Node Containers(节点容器)

2.1: Use Nodes in Node Containers(在节点容器中使用节点)

强烈建议您在节点容器中使用Texture的节点。 Texture提供以下节点容器。

Texture节点容器 UIKit等效
ASCollectionNode UICollectionView
ASPagerNode UIPageViewController
ASTableNode UITableView
ASViewController UIViewController
ASNavigationController UINavigationController 实现ASVisibility协议。
ASTabBarController UITabBarController 实现ASVisibility协议。

示例代码和特定示例项目在每个节点容器的文档中突出显示。

2.2: 我通过使用节点容器获得什么?

节点容器自动管理其节点的智能预加载。这意味着所有节点的布局测量,数据读取,解码和渲染都将以异步方式完成。 除其他便利之外,这就是为什么建议使用容器节点内的节点的原因。

请注意,虽然可以直接使用节点(没有Texture节点容器),但除非添加其他调用,否则只有在屏幕上显示时才会开始显示(如UIKit所做的那样)。 这可能导致性能下降和内容闪烁。

三: Node Subclasses

Texture提供以下节点。
与UIKit组件相比,使用节点的一个关键优势是 所有节点都可以不在主线程进行布局和显示,以便主线程可以立即响应用户交互事件。

Texture Node UIKit Equivalent
ASDisplayNode UIView 所有其他节点都从其继承的根Texture节点
ASCellNode UITableViewCell & UICollectionViewCell ASCellNode用于ASTableNode,ASCollectionNode和ASPagerNode
ASScrollNode UIScrollView 此节点对于创建包含其他节点的自定义可滚动区域很有用
ASEditableTextNode UITextView
ASTextNode UILabel
ASImageNodeASNetworkImageNodeASMultiplexImageNode UIImage
ASVideoNode AVPlayerLayer
ASVideoPlayerNode UIMoviePlayer
ASControlNode UIControl
ASButtonNode UIButton
ASMapNode MKMapView

尽管与UIKit组件大致相当,但一般而言,Texture节点提供更高级的功能和便利。 例如,ASNetworkImageNode会自动加载和缓存管理,甚至支持渐进式jpeg和动画gif。

AsyncDisplayKitOverview示例应用程序给出了上面列出的每个节点的基本实现。

三: Node Inheritance Hierarchy(节点继承层次结构)

所有texture节点都从ASDisplayNode继承。
[站外图片上传中...(image-7f1873-1521114680632)]

以蓝色突出显示的节点是UIKit元素的同步包装。例如,ASScrollNode包装一个UIScrollViewASCollectionNode包装一个UICollectionViewliveMapMode中的ASMapNodeUIMapView的同步包装器。

四: Subclassing(子类)

创建子类时最重要的区别在于您是 ASViewController还是ASDisplayNode的子类。这听起来很明显,但由于其中的一些差异很微妙,注意当前的子类

4.1: ASDisplayNode

虽然子类化节点类似于编写UIView子类,但还是有一些原则需要遵循,以确保您充分利用该框架的能力。

-init
init使用nodeBlocks时,是在后台线程上调用此方法。但是,因为在-init完成之前没有其他方法可以运行,所以在此方法中不应该有锁。

最重要的事情是你的init方法必须能够在任何队列上被调用。 最值得注意的是,这意味着您不应该初始化任何UIKit对象, 触摸节点的视图或图层(例如node.layer.xnode.view.x),或者在初始化程序中添加任何手势识别器。 相反,在-dldLoad中执行这些操作

-didLoad
这个方法在概念上类似于UIViewController-viewDidLoad方法; 它被调用一次,并且是加载后台视图的点之后。 它可以保证在主线程中调用,并且是执行任何UIKit事物(例如添加手势识别器,触摸视图/图层,初始化UIKit对象)的适当位置。

-layoutSpecThatFits:
该方法定义了布局,并在后台线程上执行繁重的计算。此方法用于构建将生成节点大小的布局规范对象,以及所有子节点的大小和位置。 这是您将放置大部分布局代码的地方。

您创建的布局规范对象具有延展性,直到它在此方法中返回为止。 在这之后,它将是不可变的。 记住不要缓存布局规格供以后使用,而是在必要时重新创建它们。

由于它在后台线程上运行,因此不应在此处设置任何node.view或node.layer属性。另外,除非您知道自己在做什么,否则不要在此方法中创建任何节点。此外,与其他方法覆盖不同,调用super的方法并不是必需的。

-layout
在这种方法中super调用是layoutSpec的结果应用的地方;在此方法调用super之后,布局规格将被计算并且所有子节点将被测量和定位。

-layout在概念上与UIViewController-viewWillLayoutSubviews类似。 这是更改隐藏属性的好地方,如果需要(不可布局的属性)设置基于视图的属性或设置背景颜色。 您可以将背景颜色设置放在-layoutSpecThatFits:中,但可能存在时间问题。 如果你碰巧使用任何UIViews,你可以在这里设置它们的框架。 但是,您始终可以使用-initWithViewBlock创建节点包装:然后在其他位置的后台线程上调整大小。

这个方法在主线程中调用。但是,如果您使用布局规范,则不应过多依赖此方法,因为最好从主线程中剔除布局。 10个小类中不到1个还可以接受。

-layout的一个很好的用途是针对特定情况,在这种情况下,您希望子节点是您的确切大小。例如。当你想要一个collectionNode占据整个屏幕。布局规范不支持这种情况,并且在此方法中使用单行手动设置框架通常最简单:

subnode.frame = self.bounds;

如果你希望在ASViewController中有同样的效果,你可以在-viewWillLayoutSubviews中做同样的事情,除非你的节点是initWithNode中的节点:在这种情况下,它会自动完成。

4.2: ASViewController

ASViewController是一个常规的UIViewController子类,它具有管理节点的特殊功能。 由于它是一个UIViewController子类,因此所有方法都在主线程上调用(并且您应该始终在主线程上创建一个ASViewController)。

4.2.1:-init
该方法在ASViewController生命周期的最初阶段被调用一次。 和UIViewController初始化一样,最好的做法是不要在这个方法中访问self.viewself.node.view。 相反,在-viewDidLoad中执行任何视图访问。

ASViewController的指定初始化程序是initWithNode :一个典型的初始化程序看起来像下面的代码。请注意在调用super之前如何创建ASViewController的节点。 ASViewController类似于UIViewController管理视图来管理节点,但初始化稍有不同。

OC
- (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;
}
swift
init() {
  let pagerNode = ASPagerNode()
  super.init(node: pagerNode)

  pagerNode.setDataSource(self)
  pagerNode.setDelegate(self)
}

4.2.2:-loadView(不推荐使用)
我们建议您不要使用这种方法,因为它与-viewDidLoad相比没有什么特别的优势,并且有一些缺点。 但是,只要不将self.view属性设置为不同的值,就可以安全使用。 对[super loadView]的调用会将其设置为node.view。

4.2.3: -viewDidLoad
这个方法在ASViewController的生命周期中被调用一次,紧接在-loadView之后。 这是您访问节点视图的最早时间。 这是放置任何设置代码的好地方,它只能运行一次,并需要访问视图/图层,例如添加手势识别器。

布局代码不应该放在这个方法中,因为当UI变化时它不会再被调用。 注意这对UIViewController同样适用;即使您目前不期望几何变化,在此方法中放置布局代码也是不好的做法。

4.2.4 -viewWillLayoutSubviews
该方法在与节点的-layout方法完全相同的时间被调用,并且可以在ASViewController的生命周期中多次调用该方法; 只要ASViewController节点的边界发生变化(包括旋转,分割屏幕,键盘显示)以及层次结构发生变化(儿童被添加,删除或更改大小),就会调用它。

为了一致性,最好的做法是将所有布局代码放入此方法中。因为它不是非常频繁地调用,甚至不直接依赖于大小的代码也属于这里。

4.2.5 -viewWillAppear: / -viewDidDisappear:
这些方法在ASViewController的节点出现在屏幕上(最早可见时)以及从视图层次结构(最早不再可见的时间)之后立即调用。 这些方法提供了一个很好的机会来启动或停止与呈现或解除控制器相关的动画。 这也是一个记录用户操作的好地方。

尽管这些方法可能被多次调用,并且几何信息可用,但它们不会被调用用于所有几何变化,因此不应该用于核心布局代码(超出特定动画所需的设置)。

五: FAQ

5.1 问题

5.1.1: 常见的开发人员错误
5.1.2: 常见的概念误解
5.1.3: 常见问题

5.1 问题回答

5.2.1: 常见的开发人员错误的回答

1: Accessing the node’s view before it is loaded
节点-init方法通常在子线程中调用,因此务必不可以访问UIKit对象,常见错误包括访问节点的视图或创建手势, 但是,这些操作非常适合在-didLoad中执行

-init中与UIKit交互可能会导致崩溃和性能问题。

2: Make sure you access your data source outside the node block(暂不明白)
indexPath参数仅在nodeBlockForItemAtIndexPath:nodeBlockForRowAtIndexPath:中返回的节点块之外有效。由于这些块是在后台线程上执行的,因此,由于数据源中的其他更改,indexPath可能因执行时间而无效。

请参阅ASTableNode页面中如何正确编码节点块的示例。与UIKit一样,如果从任何ASCellNode的块返回Nil,它将导致异常。

3: Take steps to avoid a retain cycle in viewBlocks
使用initWithViewBlock时:通过捕获对self的强引用来防止保留周期非常重要.可以创建循环的两种方法是使用块内的任何实例变量或直接引用self而不使用弱指针

只要在指向self的弱指针上访问它们,就可以使用属性而不是实例变量。

由于viewBlocks总是在主线程上执行,因此可以安全地执行UIKit操作(包括创建和添加手势识别器)。

虽然在创建视图后块被销毁,但是如果块永远不会运行并且从未创建视图,则可以保持循环以防止释放内存。

5.2.2: 常见的概念误解的回答

1: ASCellNode Reusability
Texture不使用单元重用,由于许多特定原因,这样做的一个副作用是它消除了与单元重用相关的大类错误。

2: LayoutSpecs Are Regenerated
每次调用layoutThatFits方法时,节点的layoutSpec都会重新生成。

3: Layout API Sizing
如果您对ASRelativeDimension,ASRelativeSize,ASRelativeSizeRange和ASSizeRange感到困惑,请查看我们的Layout API Sizing guide

5.2.3: 常见问题的回答

1: CALayer’s .cornerRadius Property Kills Performance
CALayercornerRadius属性的使用是灾难性,只有在没有其他选择的情况下才能使用。它是CALayer上效率最低,渲染最密集的属性之一(与shadowPath,masking,border等). 这些属性触发屏幕外渲染,以对每个帧执行剪切操作. 滚动时60FPS! - 即使该区域的内容没有变化

使用cornerRadius会在视觉上降低iPhone 4,4S和5 / 5C(以及类似的iPad / iPod)的性能,并减少头部空间并使5S和更新设备上的帧丢失更有可能

如需更长时间的讨论和简单的替代转角解决方案,请阅读 corner rounding guide

2: Texture does not support UIKit Auto Layout or InterfaceBuilder
Texture不支持UIKit Auto LayoutInterfaceBuilder。值得注意的是,这两种技术都不允许在已建立和训练有素的iOS开发团队中使用,例如Facebook,Instagram和Pinterest

但是,Texture的Layout API提供了各种ASLayoutSpec对象,允许实现更高效的自动布局(多线程,脱离主线程),更容易调试(可以进入代码并查看所有值来自哪里,因为它是开放的源)和可重用(您可以构建可以与UI的不同部分共享的可组合布局)。

3: ASDisplayNode keep alive reference

ASTextNode *title=[[ASTextNode alloc]init];
title.attributedString=Text;
[self addSubnode:title];

retain cycles
(
"-> _keepalive_node -> ASTextNode ",
"-> _view -> _ASDisplayView "
)

故意创建此保留周期,因为节点处于“实时”视图层次结构中(它位于屏幕上的UIWindow内)。

要了解为什么这是必要的,请考虑Apple还在UIView和CALayer之间创建此保留周期。如果您创建一个UIView并将其图层添加到超级图层,然后释放UIView,即使指向它的CALayer委托很弱,它也会保持活动状态。

出于同样的原因,如果节点的视图是窗口的后代,但是没有对节点的引用,我们使用视图中的强引用使节点保持活动状态。

良好的应用程序设计不应该依赖于此行为,因为应该通过子节点数组或实例变量维护对节点的强引用。但是,偶尔会出现这种情况,例如使用UIView动画API时。这个循环永远不会造成泄漏,甚至不会延长节点的生命周期,这绝对是必要的。

4: UICollectionViewCell Compatibility
Texture支持使用ASCellNodes来替换UICollectionViewCells

请注意,这些UIKit单元不具备ASCellNodes的性能优势, (如预加载,异步布局和异步绘图),即使在同一ASCollectionNode中混合使用。

但是,这种互操作性允许开发人员灵活地测试框架,而无需一次性转换所有单元。在这里阅读更多

上一篇 下一篇

猜你喜欢

热点阅读