使用AsyncDisplayKit提升UICollectionView和UITableView的滚动性能
2016-03-16 11:18
489 查看
本文由CocoaChina--夜微眠(github)翻译
作者:@Todd Kramer
原文:Improving UICollectionView & UITableView
Scrolling Performance With AsyncDisplayKit
目标:使用AsyncDisplayKit和Alamofire的异步下载、缓存以及图像解码 来提升UICollectionView的滚动性能。
上一篇教程 Downloading & Caching Images Asynchronously In Swift
With Alamofire (使用Alamofire异步下载以及缓存图片)中,我描述了如何使用Alamofire和AlamofireImage 库异步下载和缓存图片,进而显示在UICollectionView中。通过使用这些库可以轻松实现滚动流畅的滚动视图和集合视图,但是如果你的UI很复杂,图片很多,有可能不能达到60fps
此次教程中我们使用Facebook AsyncDisplayKit库重建Glacier
Scenics工程,AsyncDisplayKit有很多我们提升滚动流畅所需要的工具以及图片异步下载功能(如果你对缓存不感兴趣)。如果需要实现缓存的话,Alamofire和AlamofireImage还是可以派上用场。
AsyncDisplayKit 概览
AsyncDisplayKit用相关node类,替换了UIView和它的子类,而且是线程安全的。它可以异步解码图片,调整图片大小以及对图片和文本进行渲染。在大部分项目中,主要的目标就是实现图片异步解码。UIImage显示之前必须要先解码完成,而且解码还是同步的。尤其是在UICollectionView/UITableView 中使用 prototype cell显示大图,UIImage的同步解码在滚动的时候会有明显的卡顿。
另外一个很吸引人的点是AsyncDisplayKit可以把view层次结构转成layer。因为复杂的view层次结构开销很大,如果不需要view特有的功能(例如点击事件),就可以使用AsyncDisplayKit 的layer backing特性从而获得一些额外的提升。
AsyncDisplayKit还有很多其他的特性,最后要提到就是基于node把UICollectionView 和 UITableView 替换为 ASCollectionView 和 ASTableView 的特性。替换的类可以使用UIkit中大量的数据源和 delegate方法,这样便于你很快适应从UIKit部分到基于node架构的变化。
尽管AsyncDisplayKit基于node的架构,但每个node都有相应UIView 属性。这样你可以添加不需要与node类有交互的子视图。
设置
下图就是我们完成的工程
工程依赖
使用CocoaPods获取AsyncDisplayKit依赖,下面是Podfile
数据
图片的名称和URL从property list(plist)文件获取,分别是两个带有"name" and "imageURL"的数组。
Storyboard
项目中的Storyboard很简单,是因为AsyncDisplayKit不支持Storyboard,所以相关约束都用代码实现。我们只需要一个navigation controller 和root view controller(等下会被设成PhotosViewController)
ASCollectionView默认图片下载
第一步从plist里读取数据。我们先定义一个简单的struct GlacierScenic 存照片信息
这就可以了。下一步我们创建一个数据管理器从plist读取和存储照片信息。
接下来看下view controller代码
我们这所需要做的就是配置collection view 以及处理不同大小size classes之间的转场变化。
这里有一些注意事项:
第一,ASCollectionView使用asyncDataSource和asyncDelegate。这个很重要,因为ASCollectionView也有标准的data Source 和 delegate。所以获取数据源和委托的时候不要混淆。
第二,ASCollectionView构造器需要UICollectionViewLayout参数,但是不是所有的布局配置能生效。一个很重要的例子就是cell大小,这个需要用另外的方法处理。
最后,collectionView的布局属性有个方法invalidateLayout不起作用(问题),所以我们不使用viewWillTransitionToSize方法。
现在我们需要实现上边设置的data source
这段代码很简单。我们用一个section显示图片。接着我们返回一个新的collection view cell node (AsyncDisplayKit的 ASCellNode 子类)。
要注意的是AsyncDisplayKit 用nodeForItem 方法替换了cellForItem方法,也就需要在collection view上注册reuse identifiers。
最后就是PhotoCollectionViewCellNode。
你可能已经注意到很多代码都是layout代码。是因为AsyncDisplayKit使用动态布局机制。复杂的布局已经超出了这篇教程的范围,但如果你只需要一个固定的cell大小,那重写calculateLayoutThatFits方法就可以了。注意,计算型属性“nodeSize”代码在类的顶部。
AsyncDisplayKit使得异步下载图片变得非常简单,此外ASImageNode可以作为UIImageView的一部分,AsyncDisplayKit还有ASNetworkImageNode子类 ,你只需要把图片设置URL属性就可以了。
在这个例子中,我们还需要一个在图片下载完成时终止加载动画的加载指示器。因为ASNetworkImageNode有delegate属性,等下我们可以使用扩展来实现delegate和处理加载指示。delegate还提供了何时图片解码完成以及图片下载失败的方法。
下一步使用“AttributedTextNode”作为标题,与UILabel不同,ASTextNode没有默认字体的“text”属性,它使用attributed string。AttributedTextNode子类提供了一个实用的函数来处理node的attributed string
最后,如上所述,我们能获取cell node的view属性来添加没有相应node classes的subview 。本文例子中就有UIActivityIndicatorView 和 UIVisualEffectView
图像缓存
AsyncDisplayKit让异步图片下载变得非常简单,但是没有默认缓存支持。那么为了实现缓存,我们需要替代AsyncDisplayKit默认的下载器,所以我们用Alamofire和AlamofireImage实现下载和缓存。首先我们先更新Podfile
警告:运行前,先执行pod install
之前,我们用无参数初始化network image node。AsyncDisplayKit还有另外一个以缓存和下载器为参数的构造器。
缓存和下载器需要遵照ASImageCacheProtocol 和 ASImageDownloaderProtocol 协议。我们工程中缓存和下载及都实现在PhotosDataManager 中,所以我们需要更新PhotosDataManager以实现这些协议并提供缓存。
现在我们加了photoCache 以及两个函数,一个用于缓存图片,另外一个用于获取缓存图片。缓存最大为100MB,最优为60MB。缓存标识使用图片的URL,AsyncDisplayKit协议将会在设置network image node的URL属性后进行传递。
接着我们实现协议。第一个协议ASImageCacheProtocol就包含一个方法fetchCachedImageWithURL,用于获取缓存图片,如果对于的URL的图片存在,就返回。否则nil传给completion block,这样就会触发下载图片。
第二个协议ASImageDownloaderProtocol 包含两个方法,一个下载另一个取消下载。下载方法里我们用Alamofire Request下载图片,如果下载成功则进行缓存,然后调用 completion block。要注意的是,我们也要返回请求对象。如果取消下载,则"cancelImageDownloadForIdentifier"方法会用到它。
在取消方法里,先检查下载标识是否存在,request 是不是Request对象,然后在request上调用cancel()方法。
最后,我们替换掉PhotoCollectionViewCellNode 里ASNetworkImageNode 构造器
Layer Backing
在介绍之前,我们再加一个优化。就是AsyncDisplayKit概览中提到layer backing。它能够帮助我们通过将视图层次结构转成layer层来提升滚动性能。我们的案例中,
view/node的层次结构不太复杂,但是有两处可以添加Layer Backing。第一处就是image node,实现起来就一行代码,将layerBacked 属性设置为true。
第二处就是 container node 以及caption label subnode。
AsyncDisplayKit通过把图片解码、大小调整以及图像文本的渲染放在子线程,从而提升collectionview 和 tableview的滚动性能。也正如刚才看到的,AsyncDisplayKit默认下载不支持缓存,所以使用前需要考虑到AsyncDisplayKit一些不足的地方。
第一,AsyncDisplayKit不支持Storyboard、Xib以及Autolayout,不过并不意味着你不能在项目中使用这些工具,事实上我们依然在这个项目中使用了storyboard。如果你需要用Interface Builder和Autolayout实现collection view,那就需要另外的方法来提高流畅度。当然,如果不使用Autolayout就用程序写frame这样可以减少约束相关的消耗。总的来说,如果项目中一定要用到Autolayout,可能就要自己实现异步图片解码了。
第二,UITableView 和 UICollectionView一些重要的方法没有被AsyncDisplayKit替换或继承。在写这篇文章前,他们还处于开发阶段,有肯能会有所变化。
总的来说,无论用不用AsyncDisplayKit或者其他第三方库,这个取决于cell和collection view UI相关细节。虽然有时候你决定自己实现它的一些功能,但是该库提供
一个很好的处理UITableView 和 UICollectionView性能问题的途径。
文章中使用的项目源码存放在"GlacierScenicsAsyncDisplayKit" 文件夹下。
作者:@Todd Kramer
原文:Improving UICollectionView & UITableView
Scrolling Performance With AsyncDisplayKit
目标:使用AsyncDisplayKit和Alamofire的异步下载、缓存以及图像解码 来提升UICollectionView的滚动性能。
上一篇教程 Downloading & Caching Images Asynchronously In Swift
With Alamofire (使用Alamofire异步下载以及缓存图片)中,我描述了如何使用Alamofire和AlamofireImage 库异步下载和缓存图片,进而显示在UICollectionView中。通过使用这些库可以轻松实现滚动流畅的滚动视图和集合视图,但是如果你的UI很复杂,图片很多,有可能不能达到60fps
此次教程中我们使用Facebook AsyncDisplayKit库重建Glacier
Scenics工程,AsyncDisplayKit有很多我们提升滚动流畅所需要的工具以及图片异步下载功能(如果你对缓存不感兴趣)。如果需要实现缓存的话,Alamofire和AlamofireImage还是可以派上用场。
AsyncDisplayKit 概览
AsyncDisplayKit用相关node类,替换了UIView和它的子类,而且是线程安全的。它可以异步解码图片,调整图片大小以及对图片和文本进行渲染。在大部分项目中,主要的目标就是实现图片异步解码。UIImage显示之前必须要先解码完成,而且解码还是同步的。尤其是在UICollectionView/UITableView 中使用 prototype cell显示大图,UIImage的同步解码在滚动的时候会有明显的卡顿。
另外一个很吸引人的点是AsyncDisplayKit可以把view层次结构转成layer。因为复杂的view层次结构开销很大,如果不需要view特有的功能(例如点击事件),就可以使用AsyncDisplayKit 的layer backing特性从而获得一些额外的提升。
AsyncDisplayKit还有很多其他的特性,最后要提到就是基于node把UICollectionView 和 UITableView 替换为 ASCollectionView 和 ASTableView 的特性。替换的类可以使用UIkit中大量的数据源和 delegate方法,这样便于你很快适应从UIKit部分到基于node架构的变化。
尽管AsyncDisplayKit基于node的架构,但每个node都有相应UIView 属性。这样你可以添加不需要与node类有交互的子视图。
设置
下图就是我们完成的工程
工程依赖
使用CocoaPods获取AsyncDisplayKit依赖,下面是Podfile
图片的名称和URL从property list(plist)文件获取,分别是两个带有"name" and "imageURL"的数组。
Storyboard
项目中的Storyboard很简单,是因为AsyncDisplayKit不支持Storyboard,所以相关约束都用代码实现。我们只需要一个navigation controller 和root view controller(等下会被设成PhotosViewController)
ASCollectionView默认图片下载
第一步从plist里读取数据。我们先定义一个简单的struct GlacierScenic 存照片信息
这里有一些注意事项:
第一,ASCollectionView使用asyncDataSource和asyncDelegate。这个很重要,因为ASCollectionView也有标准的data Source 和 delegate。所以获取数据源和委托的时候不要混淆。
第二,ASCollectionView构造器需要UICollectionViewLayout参数,但是不是所有的布局配置能生效。一个很重要的例子就是cell大小,这个需要用另外的方法处理。
最后,collectionView的布局属性有个方法invalidateLayout不起作用(问题),所以我们不使用viewWillTransitionToSize方法。
现在我们需要实现上边设置的data source
要注意的是AsyncDisplayKit 用nodeForItem 方法替换了cellForItem方法,也就需要在collection view上注册reuse identifiers。
最后就是PhotoCollectionViewCellNode。
AsyncDisplayKit使得异步下载图片变得非常简单,此外ASImageNode可以作为UIImageView的一部分,AsyncDisplayKit还有ASNetworkImageNode子类 ,你只需要把图片设置URL属性就可以了。
在这个例子中,我们还需要一个在图片下载完成时终止加载动画的加载指示器。因为ASNetworkImageNode有delegate属性,等下我们可以使用扩展来实现delegate和处理加载指示。delegate还提供了何时图片解码完成以及图片下载失败的方法。
下一步使用“AttributedTextNode”作为标题,与UILabel不同,ASTextNode没有默认字体的“text”属性,它使用attributed string。AttributedTextNode子类提供了一个实用的函数来处理node的attributed string
图像缓存
AsyncDisplayKit让异步图片下载变得非常简单,但是没有默认缓存支持。那么为了实现缓存,我们需要替代AsyncDisplayKit默认的下载器,所以我们用Alamofire和AlamofireImage实现下载和缓存。首先我们先更新Podfile
之前,我们用无参数初始化network image node。AsyncDisplayKit还有另外一个以缓存和下载器为参数的构造器。
缓存和下载器需要遵照ASImageCacheProtocol 和 ASImageDownloaderProtocol 协议。我们工程中缓存和下载及都实现在PhotosDataManager 中,所以我们需要更新PhotosDataManager以实现这些协议并提供缓存。
接着我们实现协议。第一个协议ASImageCacheProtocol就包含一个方法fetchCachedImageWithURL,用于获取缓存图片,如果对于的URL的图片存在,就返回。否则nil传给completion block,这样就会触发下载图片。
第二个协议ASImageDownloaderProtocol 包含两个方法,一个下载另一个取消下载。下载方法里我们用Alamofire Request下载图片,如果下载成功则进行缓存,然后调用 completion block。要注意的是,我们也要返回请求对象。如果取消下载,则"cancelImageDownloadForIdentifier"方法会用到它。
在取消方法里,先检查下载标识是否存在,request 是不是Request对象,然后在request上调用cancel()方法。
最后,我们替换掉PhotoCollectionViewCellNode 里ASNetworkImageNode 构造器
在介绍之前,我们再加一个优化。就是AsyncDisplayKit概览中提到layer backing。它能够帮助我们通过将视图层次结构转成layer层来提升滚动性能。我们的案例中,
view/node的层次结构不太复杂,但是有两处可以添加Layer Backing。第一处就是image node,实现起来就一行代码,将layerBacked 属性设置为true。
第一,AsyncDisplayKit不支持Storyboard、Xib以及Autolayout,不过并不意味着你不能在项目中使用这些工具,事实上我们依然在这个项目中使用了storyboard。如果你需要用Interface Builder和Autolayout实现collection view,那就需要另外的方法来提高流畅度。当然,如果不使用Autolayout就用程序写frame这样可以减少约束相关的消耗。总的来说,如果项目中一定要用到Autolayout,可能就要自己实现异步图片解码了。
第二,UITableView 和 UICollectionView一些重要的方法没有被AsyncDisplayKit替换或继承。在写这篇文章前,他们还处于开发阶段,有肯能会有所变化。
总的来说,无论用不用AsyncDisplayKit或者其他第三方库,这个取决于cell和collection view UI相关细节。虽然有时候你决定自己实现它的一些功能,但是该库提供
一个很好的处理UITableView 和 UICollectionView性能问题的途径。
文章中使用的项目源码存放在"GlacierScenicsAsyncDisplayKit" 文件夹下。
相关文章推荐
- String,StringBuffer,StringBuild区别
- UIViewController的生命周期及iOS程序执行顺序
- Android studio b Error: No resource found that matches the given name (at 'src' with value '@dra...
- iOS UIlabel一些常用属性方法总结
- SystemUI之Airplane mode之分析
- 日志搜集、过滤及推送处理框架logstash及fluentd总结
- request和response中文乱码
- UIPageControl-页面控件
- String与CharSequence的区别与联系
- Unknown class ViewController in Interface Builder file问题解决
- Android Volley入门到精通:定制自己的Request
- iOS UI自动化测试与代码覆盖率
- UICollectionView不能拖拽或者刷新抖动
- ContentValues
- UILable 的字体加宽,倾斜
- UIBezierPath精讲
- Android:一个高效的UI才是一个拉风的UI(一)
- 62. Unique Paths && 63 Unique Paths II
- 仿MIUI的Toast动画效果实现
- UITableView创建