AsyncDisplayKit 初窥

了解 AsyncDisplayKit

AsyncDisplayKit的基本单位是节点 Node。一个Asdisplaynode 是 UIView 的抽象,反过来是CALayer抽象。与只能在主线程上使用的 UIView 视图不同,节点是线程安全的:您可以在后台线程上并行地实例化和配置它们的整个层次结构。
为了保持它的用户界面流畅和响应,你的应用程序应该以每秒60帧的速度呈现。这意味着主线程有六十分之一秒把每帧。这是16毫秒执行所有的布局和绘图代码!而且由于系统开销,您的代码通常不到十毫秒才能运行它导致帧下降。
AsyncDisplayKit 让你 Image 解码、文本大小和渲染,布局,和其他昂贵的UI操作关闭主线程,让主线程可以响应用户交互。

这段话翻译于 AsyncDisplayKit GitHub 的介绍, 我们可以 通过 CocoaPods or Carthage 安装使用; 另外 目前 AsyncDisplayKit 已经改名为 Texture

关于 AsyncDisplayKit 的更多深层的理解, 本人参考了以下 文章

使用

为了更加深入理解 AsyncDisplayKit 这个框架, 我尝试 进行 AsyncDisplayKit的简单使用
基于

AsyncDisplayKit 2.2.1
Swift 4.0

ASTextNode

ASTextNode 相当于 UILabel , 不同的是 它不能设置 text 只能设置 attributedText

 // MARK:- Use ASTextNode
    func buildTextNode(textContent: String)  {
        let textLabel = ASTextNode()
        if !textContent.isEmpty {
            textLabel.attributedText = NSAttributedString(string: textContent, attributes:
                [NSAttributedStringKey.foregroundColor : UIColor.white,
                 NSAttributedStringKey.font : UIFont.systemFont(ofSize: 16),
                 NSAttributedStringKey.backgroundColor : UIColor.black]
            )
        }

        let margin:CGFloat = 15
        let width:CGFloat = XW_SCREEN_WIDTH - margin * 2
        
        textLabel.layoutThatFits(ASSizeRange.init(min: CGSize.init(width: width, height: 30), max: CGSize.init(width: width, height: 300)))
        textLabel.frame = CGRect.init(x: margin, y: 90, width: textLabel.calculatedSize.width, height: textLabel.calculatedSize.height)
        
        self.view.addSubnode(textLabel)

    }

我们 可以使用 layoutThatFits 来自行计算, 自适应 label的内容 , ASSizeRange 来限定 最小 和最大的 size
然后, 设置frame 我们可以直接获取到 calculatedSize , 是不是 还是蛮方便的呢 ?
最后 使用 .addSubnode 代替 .addSubView

ASImageNode

    // MARK:- Use ASImageNode
    func buildImageNode(imageURLString: String) {
        let imageView = ASNetworkImageNode()
        imageView.frame = CGRect.init(x: 80, y: 400, width: 200, height: 200)
        imageView.backgroundColor = UIColor.green
        imageView.contentMode = .scaleAspectFill
        if !imageURLString.isEmpty {
            imageView.url = URL.init(string: imageURLString)
        }
        self.view.addSubnode(imageView)
    }

如果 你使用本地 的image 可能你只需要 使用 ASImageNode
但是加载 网络图片 就需要 使用到 ASNetworkImageNode

ASNetworkImageNode 默认用的缓存机制和图片下载器是 PinRemoteImage,为了使用我们自己的缓存机制和图片下载器,需要实现 ASImageCacheProtocol 图片缓存协议和 ASImageDownloaderProtocol 图片下载器协议两个协议

ASButtonNode

 // MARK:- Use ASButtonNode
    func buildButtonNode(buttonName: String) {
        let buttonNode = ASButtonNode()
        if !buttonName.isEmpty {
            buttonNode.setTitle(buttonName, with: UIFont.systemFont(ofSize: 14), with: UIColor.black, for: UIControlState.normal)
        }
        buttonNode.backgroundColor = UIColor.yellow
        buttonNode.frame = CGRect.init(x: 80, y: 640, width: 200, height: 30)
        buttonNode.addTarget(self, action: #selector(clickedButton(sender:)), forControlEvents: .touchUpInside)
        self.view.addSubnode(buttonNode)
    }

需要 注意 ASButtonNode 继承于 ASControlNode 可以设置 add target, 有属性
ASTextNode * titleNode; ASImageNode * imageNode; ASImageNode * backgroundImageNode;

ASControlNode

ASImageNode、ASButtonNode、ASTextNode 同为 ASControlNode 子类,可以直接使用 .addTarget(self, action: "handleXXX", forControlEvents: .TouchUpInside) 为它们添加点击响应事件,而避免使用addGesture等方法。

ASTableNode

我们可以使用 ASTableNode 来 着力解决 UITableView 在 ReloadData 耗时长以及滑动卡顿的性能问题

初始化 let tableView = ASTableNode.init(style: UITableViewStyle.plain)
代理 ASTableViewDataSource, ASTableViewDelegate

ASTableDataSource

 //MARK:- ASTableDataSource
    
    func numberOfSections(in tableNode: ASTableNode) -> Int {
        return 1
    }
    
    func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
        return mockData.count
    }
    
    func tableNode(_ tableNode: ASTableNode, nodeForRowAt indexPath: IndexPath) -> ASCellNode {
        let cellNode = CustomCellNode()
        return cellNode
     }
     
     .....

ASTableDataSource

 //MARK:-  ASTableDelegate
    func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) {
 
    }
 
    func tableNode(_ tableNode: ASTableNode, willDisplayRowWith node: ASCellNode) {
    
  }
  
  ..... 

需要注意 ASTableNode 的高度计算以及布局都在 ASCellNode 中实现,与 ASTableNode 是完全解耦的。
ASTableNode 中所有的元素都不支持 AutoLayout、AutoResizing,也不支持StoryBoard、IB。
ASTableNode 完全可以将滑动性能提升至60FPS。
ASTableNode 实质上是一个 ScrollView ,其中添加有指定数的 ASDisplayNode,在屏幕滚动时,离屏的ASDisplayNode内容会被暂时释放,在屏或接近在屏的ASDisplayNode会被提前加载。因此,ASTableView 不存在 Cell 复用的问题,也不存在任何 Cell 复用。

ASCellNode

通常我们 自定义 UITableViewCell 是继承于 ASCellNode
执行以下步骤

  • override init() 创建 对应 的 node, 添加进去
  • 添加数据 function
  • override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize 计算控件宽度和高度,并返回 Cell 的高度, 我们一般 需要计算的 东西 都是在这个方法里面进行 layoutThatFits
  • override func layout() 各个控件 进行布局 , 通常 调用 calculatedSize 进行 自适应布局

ASDisplayNode

对于一些 不支持 的控件, 列如 UISlider, UISwitch, UIActivityIndicatorView, 并没有对应的 ASDisplayNode 子类实现。因此,我们需要创建一个 ASDisplayNode ,使用block方法返回;

这里以一个 UIActivityIndicatorView 为例

    let activityIndicator = ASDisplayNode.init(viewBlock: { () -> UIView in
        let view = UIActivityIndicatorView(activityIndicatorStyle: .gray)
        view.backgroundColor = UIColor.clear
        view.hidesWhenStopped = true
        return view
    })

使用同样的方法,可以添加任意类型 UIView 到 CellNode 中,这样就不需要被 AsyncDisplayKit 束缚我们的应用了。

使用感想

首先, 本文只是对 AsyncDisplayKit 的一些基础 控件 进行初窥使用; 通过 Node 的使用方式 和 UIView 有着 相似 的一些 代码 特性; Node 会暴露 出 view 的一些基础属性 和 UIView 使用一样, 这令我们的学习成本 有一定降低; 一些不会明显暴露的属性,也可以通过 node.view 来获取到, 让初次接触 框架的人 比较容易上手。

其次, AsyncDisplayKit 是线程 绝对 安全的, 能保证 一些复杂的界面也稳定 60fps 运行, 开发者不比去考虑 Image 解码、文本大小和渲染等 带来的线程堵塞, 有利于程序的性能优化。 同事 使用 AsyncDisplayKit 来开发, 代码的条理 变得 清晰, 通过 ASCellNode 能够完全和 ASTableNode 进行解耦来看, 各种优点会让程序变得更加容易维护。

于此同时, AsyncDisplayKit 带来的负面影响也是不容忽视的;由于ASDK的基本理念是在需要创建UIView时替换成对应的Node来获取性能提升,因此对于现有代码改动较大,侵入性较高,同时由于大量原本熟悉的操作变成了异步的,对于一个团队来说学习曲线也较为陡峭。

AsyncDisplayKit 不支持 AutoLayout、AutoResizing,也不支持StoryBoard、IB。这 似乎 与 Apple 的理念相违背; Apple 是鼓励使用 可视化进行编程的。

综合来分析, AsyncDisplayKit 是一个非常的不错框架; 考虑到AsyncDisplayKit的种种好处,非常推荐AsyncDisplayKit,当然还是仅限于用在比较复杂和动态的页面中。不需要也不可能将所有UIView都替换成其Node版本。将注意力集中在可能造成主线程阻塞的地方,如tableView/collectionView、复杂布局的View、使用连续手势的操作等等。

Link

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

推荐阅读更多精彩内容