一: 智能预加载
二: Node Containers
三: Node Subclasses
四: Subclassing
五: FAQ
一: 智能预加载
虽然Texture的异步渲染和异步测量的能非常强大,但对于Texture还有一个非常重要特点是智能预加载。
正如“入门指南”中指出的那样,在节点容器的上下文之外使用节点将体现不出Texture的优势。这是因为只有接点容器才可以获取所有节点当前接口状态。
此接口状态属性由ASRangeController
根据节点的状态不断更新,节点容器管理节点的创建和维护
在容器外部使用的节点不能正常使用和更新其状态。这有时会导致闪烁,这种情况是没有警告⚠️
1.1: Interface State Ranges
当节点添加到滚动或分页界面时,它们通常位于以下几种状态中的一种。随着滚动视图的滚动,它们的接口状态将在它们滚动时不断更新。
节点将处于以下范围之一中:
接口状态 | 描述 |
---|---|
Preload | 远离可见的最远距离。这是从外部来源收集内容的地方,无论是API还是本地磁盘。 |
Display | 在这里,显示任务,如文本光栅化和图像解码。 |
Visible | 该节点在屏幕上至少有一个像素。 |
1.2: ASRangeTuningParameters
每个这些范围的大小以“screenfuls”来衡量。尽管使用默认大小在很多用例中也能很好地工作,但可以通过在screenfuls
为范围类型设置调整参数来轻松调整它们。
在上面的可视化滚动视图中,用户正在向下滚动。如您所见,将要滚动方向上的screenfuls
大小比用户远离的拖尾方向大得多。 如果用户要改变方向,则前进和拖尾状态将动态交换以保持内存使用最佳。 这使您可以担心定义前导和尾随尺寸,而无需担心对用户变化的滚动方向作出反应。
智能预加载还可以在多个维度上运行
1.3: 接口状态的回调
当用户滚动时,节点在范围内移动并做出适当反应。您自己的节点子类可以通过实现相应的回调方法来轻松利用此机制。
-
1: Visible Range
-didEnterVisibleState
-didExitVisibleState
-
2: Display Range
-didEnterDisplayState
-didExitDisplayState
-
3: Preload Range
-didEnterPreloadState
-didExitPreloadState
二: 节点容器
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所做的那样)。 这可能导致性能下降和内容闪烁体现不出Texture的优势
三: Node Subclasses
Texture提供以下节点。
与UIKit组件相比,使用节点的一个核心优势是 所有节点都可以不在主线程进行布局和显示,因此主线程可以及时响应用户交互事件。
Texture Node | UIKit Equivalent |
---|---|
ASDisplayNode | 替代UIView Texture节点的基类 |
ASCellNode | 替代UITableViewCell & UICollectionViewCell ASCellNode用于ASTableNode,ASCollectionNode和ASPagerNode
|
ASScrollNode | 替代UIScrollView 此节点对于创建包含其他节点的自定义可滚动区域很有用 |
ASEditableTextNode | 替代UITextView
|
ASTextNode | 替代UILabel
|
ASImageNode | 替代UIImageView
|
ASNetworkImageNode | |
ASMultiplexImageNode | |
ASVideoNode | 替代AVPlayerLayer
|
ASVideoPlayerNode | 替代UIMoviePlayer
|
ASControlNode | 替代UIControl
|
ASButtonNode | 替代UIButton
|
ASMapNode | 替代MKMapView
|
尽管与UIKit组件大致相当,但一般而言,Texture节点提供更高级的功能和便利。 例如,ASNetworkImageNode
会自动加载和缓存管理,支持渐进式jpeg和动画gif。
AsyncDisplayKitOverview示例应用程序给出了上面列出的每个节点的基本实现。
三: Node Inheritance Hierarchy(节点继承层次结构)
所有texture节点都从ASDisplayNode继承。
[图片上传失败...(image-a3290d-1569489726113)]
以蓝色突出显示的节点是UIKit元素的同步包装。例如,ASScrollNode
包装一个UIScrollView
,ASCollectionNode
包装一个UICollectionView
。 liveMapMode中的ASMapNode
是UIMapView
的同步包装器。
四: Subclassing(子类)
创建子类时最重要的区别在于您是 ASViewController
还是ASDisplayNode
的子类。这听起来很明显,但由于其中的一些差异很微妙,注意当前的子类
4.1: ASDisplayNode
虽然子类化节点类似于编写UIView子类,但还是有一些原则需要遵循,以确保您充分利用该框架的能力。
1:
init
使用nodeBlocks
子线程
2: didLoad 主线程
3: layoutSpecThatFits 子线程
4: layout 主线程
-init
init
使用nodeBlocks
构建方法是在后台线程上调用此方法。但是,因为在-init
完成之前没有其他方法可以运行,所以在此方法中没有必要使用锁。
最重要的事情是你的init
方法必须能够在任何队列上被调用。 最值得注意的是,这意味着您不应该初始化任何UIKit
对象, 触摸节点的视图或图层(例如node.layer.x
或node.view.x
),或者在初始化程序中添加任何手势识别器。 相反,在-dldLoad
中执行这些操作。
-didLoad
这个方法在概念上类似于UIViewController
的-viewDidLoad
方法; 它被调用一次,并且是加载背景视图之后。 didLoad
在主线程中调用,并且是执行任何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
子类,因此所有UIViewController
方法都在主线程上调用(并且您应该始终在主线程上创建一个ASViewController
)。
4.2.1:-init
该方法在ASViewController
生命周期的最初阶段被调用一次。 和UIViewController
初始化一样,最好的做法是不要在这个方法中访问self.view
或self.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: 常见的开发人员错误
-
1: * Do not access a node's view in
-init:
.
节点-init
方法通常在子线程中调用,因此务必不可以访问UIKit对象,常见错误包括访问节点的视图或创建手势, 但是,这些操作非常适合在-didLoad
中执行在
-init
中与UIKit
交互可能会导致崩溃和性能问题。 -
2: * Make sure you access your data source outside of a
nodeBlock
.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.1.2: 常见的概念误解
-
1: *
ASCellNodes
are not reusable.Texture
不使用cell重用,由于许多特定原因,这样做的一个副作用是它消除了与单元重用相关的大量错误。 -
2: * Layout Specs are regenerated each time layout is called.
每次调用
layoutThatFits
方法时,节点的layoutSpec
都会重新生成。 -
3: * The difference between all of the sizes used in our powerful Layout API.
如果您对
ASRelativeDimension
,ASRelativeSize
,ASRelativeSizeRange
和ASSizeRange
感到困惑,请查看我们的Layout API Sizing guide
5.1.3: 常见问题
-
CALayer
的cornerRadius
属性的使用是灾难性,只有在没有其他选择的情况下才能使用。它是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.
Texture
不支持UIKit Auto Layout
和InterfaceBuilder
。值得注意的是,这两种技术都不允许在已建立和训练有素的iOS开发团队中使用,例如Facebook,Instagram和Pinterest
但是,Texture的Layout API提供了各种ASLayoutSpec对象,允许实现更高效的自动布局(多线程,脱离主线程),更容易调试(可以进入代码并查看所有值来自哪里,因为它是开放的源)和可重用(您可以构建可以与UI的不同部分共享的可组合布局)。
-
3: * Can I use my
UICollectionViewCells
with Texture?.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: *
ASDisplayNode
keep alive reference.Texture
支持使用ASCellNodes
来替换UICollectionViewCells
请注意,这些UIKit单元不具备
ASCellNodes
的性能优势, (如预加载,异步布局和异步绘图),即使在同一ASCollectionNode
中混合使用。但是,这种互操作性允许开发人员灵活地测试框架,而无需一次性转换所有单元。在这里阅读更多。