AsyncDisplaykit(Texture)技术分享

27 篇文章 0 订阅

AsyncDisplaykit(Texture)技术分享

官方Texture文档:https://texturegroup.org/docs/getting-started.html

github: https://github.com/TextureGroup/Texture
可以下载github里example的代码看

UIKit的绘制机制图解

CALayerdisplay方法由系统调用,用来更新layer的内容,如果layerdelegate对象,那么display方法将尝试调用delegatedisplayer:方法来更新layer的内容。如果delegate没有实现displaylayer:方法,则这个方法会创建一个backing store来保存原来的内容,然后调用layerdrawInContext:方法来填充back store。最后以新的back store替换layer之前内容达到更新layer的目的。通常UIKitCAlayerdelegateUIView对象

两种方式自定义CAlayer的内容

请添加图片描述

有时一个 layer会包含很多 sub-layer,而这些sub-layer并不需要响应触摸事件,也不需要进行动画和位置调整。ASDK为此实现了一个被称为pre-composing的技术,可以把这些sub-layer合成渲染为一张图片。开发时,ASNode已经替代了UIViewCALayer;直接使用各种Node控件并设置为layer backed后,ASNode甚至可以通过预合成来避免创建内部的UIView和CALayer。
通过这种方式,把一个大的层级,通过一个大的绘制方法绘制到一张图上,性能会获得很大提升。CPU避免了创建UIKit对象的资源消耗,GPU避免了多张texture合成和渲染的消耗,更少的bitmap也意味着更少的内存占用。

Node异步绘制

请添加图片描述

RunLoop机制

Run Loops运行循环。一个run loop是用来在先冲上管理事件异步到达的基础设施。一个run loop为线程监测一个或多个事件源。当事件到达的时候,系统唤醒线程并调度事件到run loop,然后分配给指定程序。如果没有事件出现和准备处理,run loop把线程置于休眠状态。

请添加图片描述

iOS一个runloop循环为1/60秒

ASNode把任务用ASAsyncTransaction(Group)封装并提交到一个全局的容器,并注册一个比coreAnimation优先级低的Observe,coreAnimation执行任务后再执行Node的任务的内容,并在合适的机会异步并发同步到主线程

Texture允许您将图像解码、文本大小和渲染以及其他昂贵的UI操作从主线程上移动,以保持主线程响应用户交互。ASLayoutSpec为每个节点提供一个来执行异步测量和布局

texture使用Flex布局(不是很懂,布局格式可以参考https://bawn.github.io/2017/12/Texture-Layout/?utm_source=tuicool&utm_medium=referral)

看图看似比autolayout快多了,跟frame布局相差无几

img

import AsyncDisplayKit 开启texture库

UIKit的控件和AS控件对应

TextureUIKit
ASDisplayNodeUIView
ASCellNodeUITableViewCell/UICollectionViewCell
ASTextNodeUILabel
ASImageNodeUIImageView
ASNetworkImageNodeUIImageView
ASVideoNodeAVPlayerLayer
ASControlNodeUIControl
ASScrollNodeUIScrollView
ASControlNodeUIControl
ASEditableTextNodeUITextView
ASMultiplexImageNodeUIImageView
UITableViewASTableView
UICollectionViewASCollectionView
TextureUIKit
ASViewControllerUIViewController
ASTableNodeUITableView
ASCollectionNodeUICollectionView
ASPagerNodeUICollectionView

使用ASViewController的好处:

  1. 保存内存。屏幕关闭的ASViewController将自动缩小其任何子数据的获取数据的大小和显示范围。这是大型应用程序内存管理的关键。
  2. ASVisibility功能。当在ASNavigationControllerASTabBarController中使用时,这些类知道使视图控制器可见所需的用户点击的确切数量。

布局

https://bawn.github.io/2017/12/Texture-Layout/?utm_source=tuicool&utm_medium=referral

https://www.jianshu.com/p/ecb682a6cde0

    • 布局规则说明
      ASInsetLayoutSpec插入布局
      ASOverlayLayoutSpec覆盖布局
      ASBackgroundLayoutSpec背景布局
      ASCenterLayoutSpec中心布局
      ASRatioLayoutSpec比例布局
      ASStackLayoutSpec堆叠布局
      ASAbsoluteLayoutSpec绝对布局

布局元素属性

属性类型描述
.style.widthASDimension设置元素的宽度。 会被minWidth和maxWidth覆盖。默认为ASDimensionAuto
.style.heightASDimension设置元素的高度。 会被minHeight和maxHeight覆盖。默认为ASDimensionAuto。
.style.minHeightASDimension设置元素的最大高度。 它防止height属性的已使用值变得大于为maxHeight指定的值。 maxHeight的值覆盖height,但minHeight覆盖maxHeight。默认为ASDimensionAuto
.style.maxHeightASDimension如果子元素的堆栈大小的总和大于最大大小
.style.minWidthASDimension设置元素的最小宽度。它防止width属性的使用值变得小于为minWidth指定的值。 minWidth的值覆盖maxWidth和width。默认为ASDimensionAuto
.style.maxWidthASDimension设置元素的最大宽度。 它防止width属性的使用值变得大于为maxWidth指定的值。 maxWidth的值覆盖width,但minWidth覆盖maxWidth。默认为ASDimensionAuto
.style.preferredSize**CGSize **提供布局元素的建议大小。 如果提供了可选的minSize或maxSize,且preferredSize超过这些,则将强制执行minSize或maxSize, 如果未提供此可选值,则布局元素的大小将默认为其提供的内在内容大小calculateSizeThatFits:

网址有个很好的例子

请添加图片描述

代码

 override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
        
        self.node1.style.preferredSize = CGSize(width: constrainedSize.max.width, height: 136)
        
        
        self.node2.style.preferredSize = CGSize(width: 58, height: 25)
        self.node2.style.layoutPosition = CGPoint(x: 14.0, y: 95.0)
        
        self.node3.style.height = ASDimensionMake(37.0)
        self.node4.style.preferredSize = CGSize(width: 80, height: 20)
        self.node5.style.preferredSize = CGSize(width: 80, height: 20)
        
        self.node4.style.spacingBefore = 14.0
        self.node5.style.spacingAfter = 14.0
        
        let absoluteLayout = ASAbsoluteLayoutSpec(children: [self.node2])
        
        let overlyLayout = ASOverlayLayoutSpec(child: self.node1, overlay: absoluteLayout)
        
        let insetLayout = ASInsetLayoutSpec(insets: UIEdgeInsetsMake(0, 14, 0, 14), child: self.node3)
        insetLayout.style.spacingBefore = 13.0
        insetLayout.style.spacingAfter = 25.0
        
        
        let bottomLayout = ASStackLayoutSpec.horizontal()
        bottomLayout.justifyContent = .spaceBetween
        bottomLayout.alignItems = .start
        bottomLayout.children = [self.node4, self.node5]
        bottomLayout.style.spacingAfter = 10.0
//        bottomLayout.style.width = ASDimensionMake(constrainedSize.max.width)
        
        
        let stackLayout = ASStackLayoutSpec.vertical()
        stackLayout.justifyContent = .start
        stackLayout.alignItems = .stretch
        stackLayout.children = [overlyLayout, insetLayout, bottomLayout]
        
        return stackLayout
    }

自己做了一个demo

请添加图片描述

​ 图片使用的是ASNetworkImageNode

let imageView: ASNetworkImageNode = {
        let v = ASNetworkImageNode(cache: ASNetImageManage.shared, downloader: ASNetImageManage.shared)
        v.cornerRadius = 10
        v.contentMode = .scaleAspectFill
        return v
    }()

这里我使用了sdwebimage的代理

ASNetworkImageNode(cache: ASNetImageManage.shared, downloader: ASNetImageManage.shared)

改写ASNetworking的cache和downloader为sdwebimage的下载缓存机制

import SDWebImage
import AsyncDisplayKit
extension ASNetworkImageNode {
    static func imageNode() -> ASNetworkImageNode {
        return ASNetworkImageNode(cache: ASNetImageManage.shared, downloader: ASNetImageManage.shared)
    }
}

class ASNetImageManage: NSObject, ASImageDownloaderProtocol, ASImageCacheProtocol {
    
    static let shared = ASNetImageManage()
    
    func downloadImage(with URL: URL, callbackQueue: DispatchQueue, downloadProgress: ASImageDownloaderProgress?, completion: @escaping ASImageDownloaderCompletion) -> Any? {
        
        weak var weakOperation: SDWebImageOperation?
        let operation = SDWebImageManager.shared.loadImage(with: URL, options: .retryFailed, progress: { (received, expected, url) in
            if downloadProgress != nil {
                callbackQueue.async(execute: {
                    let progress = expected == 0 ? 0 : received / expected
                    downloadProgress?(CGFloat(progress))
                })
            }
        }) { (cachedImage, data, error, type, unknow, url) in
            if let image = cachedImage {
                callbackQueue.async(execute: { completion(image, nil, nil, nil) })
                return
            }
            callbackQueue.async(execute: { completion(nil, error, nil, nil) })
        }
        weakOperation = operation
        return weakOperation
    }
    
    func cancelImageDownload(forIdentifier downloadIdentifier: Any) {
        if let downloadIdentifier = downloadIdentifier as? SDWebImageOperation {
            downloadIdentifier.cancel()
        }
    }
    
    func cachedImage(with URL: URL, callbackQueue: DispatchQueue, completion: @escaping ASImageCacherCompletion) {
                
        if let key = SDWebImageManager.shared.cacheKey(for: URL) {
            SDWebImageManager.shared.imageCache.queryImage(forKey: key, options: .allowInvalidSSLCertificates, context: nil) { (cachedImage, data, type) in
                if let image = cachedImage {
                    callbackQueue.async(execute: { completion(image, ASImageCacheType.asynchronous) })
                    return
                }
                callbackQueue.async(execute: { completion(nil, ASImageCacheType.asynchronous) })
            }
        }else {
            callbackQueue.async {
                completion(nil, ASImageCacheType.asynchronous)
            }
        }
    }
}

UILabel

let textView: ASTextNode = {
        let v = ASTextNode()
        
        v.maximumNumberOfLines = 0
        return v
    }()

ASTextNode不自带font和textcolor,但也能从.view.font制作,他只提供了AttributeString的方法

textView.attributedText = NSAttributedString(string: model.name ?? "", attributes: convertToOptionalNSAttributedStringKeyDictionary(([convertFromNSAttributedStringKey(NSAttributedString.Key.font): UIFont.customFont(.pingFangSCRegular, ofSize: 14),
        convertFromNSAttributedStringKey(.foregroundColor): UIColor.lightGray])))

写cell以前是用

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
  super.init(style: style, reuseIdentifier: reuseIdentifier)

ASCellNode可以直接用来写,因为他是异步优化,所以model的传值是安全的,里面可以直接写数据的变化

init(model: listModel) {
        super.init()

布局就要使用override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {

    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
        
        //imageView大小(宽为屏幕宽度-60,高为model给的高,为了把tableview变复杂)
        self.imageView.style.preferredSize = CGSize(width: KSCREENWIDTH - 60, height: CGFloat(photoHeight))
        //imageView插入布局(上左右间距12px)
        let imageLayout = ASInsetLayoutSpec(insets: UIEdgeInsets(top: 12, left: 12, bottom: 0, right: 12), child: self.imageView)
        //middleLine高1px
        self.middleLine.style.height = ASDimensionMake(1)
        //middleLine插入布局(左右间距0.5)
        let lineLayout = ASInsetLayoutSpec(insets: UIEdgeInsets(top: 0, left: 0.5, bottom: 0, right: 0.5), child: middleLine)
        //middleLine布局对比上一个view间距11
        lineLayout.style.spacingBefore = 11
        //middleLine布局对比下一个view间距0
        lineLayout.style.spacingAfter = 0
        
        //textView插入布局(左14,右40)
        let insetLayout = ASInsetLayoutSpec(insets: UIEdgeInsets(top: 0, left: 14, bottom: 0, right: 40), child: self.textView)
        //上边距
        insetLayout.style.spacingBefore = 13
        //下边距
        insetLayout.style.spacingAfter = 25
        //以垂直结构布局全部,第一个为imageview第二个为middleLine,第三个为textView
        let stackLayout = ASStackLayoutSpec.vertical()
        stackLayout.justifyContent = .start
        stackLayout.alignItems = .stretch
        stackLayout.children = [imageLayout, lineLayout, insetLayout]
        
        return stackLayout
    }

写完cell就直接写tableview(ASTableNode)

如果UIKit的东西有但AsyncDisplayKit没有,可以使用.view.来访问uikit

ASTableNode有自带的上下拉加载数据的delegate,但写的很烂

func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) {

直接用MJRefresh带的库好用

super.init(node: tableView)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.view.mj_header = MJRefreshNormalHeader(refreshingBlock: {
            
        })
        tableView.view.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {
            
        })
        tableView.view.separatorStyle = .none

tableNode是没有类似设置高度的方法,他是自动高度计算

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

设置cell的个数和cell的样式是差不多,但要注意的是cell的设置是闭包return

//设置cell的个数
func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
        return resultModel?.list?.count ?? 0
    }
//设置cell的样式
    func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
        return { [weak self] in
            if let model = self?.resultModel?.list?[indexPath.row] {
                let node = TextureCell(model: model)
                node.selectionStyle = .none
                node.goAction = { [weak self] in
                    self?.videoPlay()
                }
                return node
            }else {
                return ASCellNode()
            }
        }
    }

demo
Dropbox地址,如果没有翻墙的话可以私信我发给你
https://www.dropbox.com/sh/q0xrr6hcad4orwr/AAD1kfuyndVQqpQ6HPfLqWuHa?dl=0

引用文章

https://bawn.github.io/2017/12/Texture-Layout/?utm_source=tuicool&utm_medium=referral (Texture 布局篇)

https://www.jianshu.com/p/ecb682a6cde0 (AsyncDisplaykit(Texture)之布局篇)

https://blog.csdn.net/quanqinyang/article/details/54799517 (AsyncDisplayKit 系列教程 —— 集成、示例)这篇有点旧,代码出入比较大

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值