Texture核心点(B)

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

一: 智能预加载

虽然Texture的异步渲染和异步测量的能非常强大,但对于Texture还有一个非常重要特点是智能预加载。

正如“入门指南”中指出的那样,在节点容器的上下文之外使用节点将体现不出Texture的优势。这是因为只有接点容器才可以获取所有节点当前接口状态。

此接口状态属性由ASRangeController根据节点的状态不断更新,节点容器管理节点的创建和维护

在容器外部使用的节点不能正常使用和更新其状态。这有时会导致闪烁,这种情况是没有警告⚠️

1.1: Interface State Ranges

当节点添加到滚动或分页界面时,它们通常位于以下几种状态中的一种。随着滚动视图的滚动,它们的接口状态将在它们滚动时不断更新。

接口状态范围

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

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

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

image

在上面的可视化滚动视图中,用户正在向下滚动。如您所见,将要滚动方向上的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包装一个UIScrollViewASCollectionNode包装一个UICollectionViewliveMapMode中的ASMapNodeUIMapView的同步包装器。

四: Subclassing(子类)

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

4.1: ASDisplayNode

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

1: init使用nodeBlocks 子线程
2: didLoad 主线程
3: layoutSpecThatFits 子线程
4: layout 主线程

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

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

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

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

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

由于它在后台线程上运行,因此不应在此处设置任何node.viewnode.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.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: 常见的开发人员错误
  • 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: 常见的概念误解
5.1.3: 常见问题
  • 1: * If you care about performance, do not use CALayer's .cornerRadius property (or shadowPath, border or mask).

    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.

    Texture不支持UIKit Auto LayoutInterfaceBuilder。值得注意的是,这两种技术都不允许在已建立和训练有素的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中混合使用。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容