内容简介:..
GIC
在 UI元素
以及 布局系统
都是基于 Texture
开发的,这里可能需要做下说明。
. UI元素
指的是 lable
、 list
、 image
这些可以直接显示内容的元素。
. 布局系统
指的是专门用来布局的面板,同时也是一种特殊的UI,你可以理解为一个一种布局面板就是一种 UIView
,因此也可以设置 background-color
、 height
、 width
等属性。
下面如无特殊说明, UI元素
指的是包括 布局系统
在内的元素。而所有 UI元素
都将拥有共同的基础属性。
在正式介绍 GIC
是如何基于 Texture
开发之前,先给各位看一个例子。
假如我们要在项目中做一个类似这样布局的 TableView
,而且还要求其中的图片宽高比必须是16:9,标题最多显示两行,而且还要求适配横屏和竖屏的切换,对于这样的需求,我们会怎么去设计我们的架构,如何去实现这样的需求?做过类似需求的同学应该深有体会这样的布局是多么的复杂,更不用说一个列表中可能每个cell的布局方式都不一样的。而我们在开发这样的应用的时候就会把大量的时间花费在布局代码上,而且还要考虑性能进而准备各种性能优化措施。
然后看下使用 GIC
以后你所需要的布局代码。
<!--创建一个页面--> <page> <!--创建一个tableView,并且将一个包含5个item的数组作为数据源--> <list data-context='[1,2,3,4,5]'> <!--创建一个section--> <section> <!--创建for指令,用来根据数据源循环创建list-item,这里面数据源包含了5个item,也就是说会创建5个list-item--> <for> <!--创建list-item,可以理解为UITableViewCell--> <list-item separator-inset = "0 0 0 0"> <!--创建一个内边距为12的布局面板--> <inset-panel inset="12"> <!--创建一个在垂直方向从上到下自动布局的面板--> <stack-panel> <!--创建标题,并且设置颜色、大小、最多显示行数等属性--> <lable text="国际社会积极评价习近平主席在金砖国家工商论坛上的重要讲话" lines="2" font-color="191919" font-size="24"/> <!--创建一个宫格布局的面板,每行显示3个,并且每列之间的间隔为10--> <grid-panel columns="3" column-spacing="10" space-before="10" data-context='[1,2,3]'> <!--创建一个for指令,根据data-context的数组内容,自动循环创建for包含的元素--> <for> <!--创建一个比例布局面板,并且将比例设置为16:9--> <ratio-panel ratio="0.5625"> <!--创建一个iamge元素,用来显示图片--> <image url="http://img5.duitang.com/uploads/item/201204/01/20120401222440_eEjyC.thumb.700_0.jpeg"/> </ratio-panel> </for> </grid-panel> <!--底部信息--> <!--创建一个水平方向布局的面板,并且规定每个元素的间隔为10--> <stack-panel is-horizon="1" space="10" space-before="10"> <lable text="人民网" font-color="999999"/> <lable text="15评论" font-color="999999"/> <lable text="1小时前" font-color="999999"/> </stack-panel> </stack-panel> </inset-panel> </list-item> </for> </section> </list> </page> 复制代码
上面的XML代码就能完美的实现上述的需求,你可以数下元素,总共才创建了 16
个元素,包含了从创建页面到创建 UITableView
到最终cell的布局所有过程。你可以直接将这段XML代码写入一个XML文件,然后让 GIC
加载出来。如果你切换横竖屏的话还会发现列表内容也会自动切换,并且图片的比例严格按照16:9显示。这样的布局代码,在UI设计稿确定并且熟悉各种布局面板的前提下,你只需要几分钟就能写出来。
这样的开发效率跟传统的开发方式相比简直是天壤之别,而且自动继承 Texture
的高性能,实际的渲染速度非常快。 GIC
在 Texture
的基础上进一步的优化了列表的加载速度,使得列表的首屏显示速度控制在毫秒级,比如在iphone6p上,这样的布局首屏显示只需要大概60毫秒,哪怕是iphone4这样的手机也能取得90毫秒的优异成绩
下面进入正题,介绍下 GIC
是如何整合 Texture
的。
由于 Texutre
在布局以及渲染上可谓是自成一体,你甚至可以将之理解为另外一套独立的 "UIView"
。使用 Texutre
可以获取以下两个优势:
-
优秀的布局系统。(区别于autolayout)。
这套布局系统不仅在性能上比
autolayout
具有优势,而且还提供多种不同的强大布局方式,比如:支持FlexBox
布局的ASStackLayoutSpec
。而且基于此,你可以自己开发自己的布局系统。用了Texture
以后,你几乎可以跟frame
、autolayout
说拜拜了。 -
异步渲染能力。
Texture
区别UIView
的一大特色功能就是支持background
渲染。我们都知道,UIView
只能在主线程上使用,而我们平常说的界面卡顿大多数情况是由于主线程被阻塞引起的,因此我们常规的优化方式就是极力减少主线程的压力。而Texture
天生就支持background
布局、渲染,在现在设备多核CPU的加持下,可以并行渲染、布局,这极大的提升了我们应用的性能。稍微夸张的说,在你使用Texture
的情况下,你几乎不用再为性能发愁,而平常我们为优化性能准备的各种优化措施在Texture
面前将会显得没有用武之地。注:
Texutre
能够支持background
渲染的一个技术基础就是CoreGraphics
,因为CoreGraphics
是线程安全的,因此你可以直接在非UI线程下进行CoreGraphics
的调用。 另外,对于异步布局来说,因为Texture
的布局系统本来就是独立的一套,虽然最终还是会反应在frame上,但是在做布局计算的时候是独立于UIView以外的。
Texture
的功能肯定不止上面这两点,但我个人觉得,光是这两个优势就能足以让我决定在实际的项目中使用了。可能有人会说, Texture
虽然布局系统强大,但是使用起来却并不是很方便,或者说上手难度不容易,对于这点其实我也不否认,但是当你在 GIC
中直接使用XML来开发界面的时候你就会发现,UI布局原来也可以这么舒服(参考上面的XML代码)。
GIC
在对 Texture
进行二次封装的过程中,分为三个部分。
- 对布局系统的封装
- 对ASDisplayNode封装。
- 对ASTableNode以及ASCollectionNode的封装。
下面一一进行介绍说明
一、对布局系统的封装
Texture
的布局类都是继承自 ASLayoutSpec
的,从而衍生出各种不同的布局类。而 GIC
中的每一种布局面板基本上都是对应了一种 ASLayoutSpec
,但是 GIC
在此基础上做了进一步的封装,那就是每一种布局面板都继承自 ASDisplayNode
,这样一来每一种布局面板都能单独设置 background-color
、 width
、 height
等属性,使得每一种布局面板你都可以作为一个单独的 UIView
来对待。
之所以这么做是有原因的,举个例子。就拿上面那个布局举例:
要给整个cell设置一个背景色,然后添加一个圆角。如果布局面板仅仅只是对 ASLayoutSpec
进行封装的话,那么就无法设置背景色和圆角的属性,因为对于 Texture
来说,只有 ASDisplayNode
才能设置这样的属性,因此 GIC
在综合考虑之下,决定直接每个面板都继承自 ASDisplayNode
,这样每一种面板都能即提供布局功能,又能提供 ASDisplayNode
等各种属性设置的能力,可谓一举两得。
<inset-panel inset="12" background-color="00000033" corner-radius="10"> 复制代码
这样,你在写布局代码的同时又能给布局面板设置各种属性。
而 GIC
整个的布局系统的类图大概如下:
目前 GIC
共提供了7中布局面板,每一种面板都对应一种 ASLayoutSpec
,每一种布局面板的使用方法可以从文档上查看
对于每一种布局面板来说,所要做的事情就是在 layoutSpecThatFits
方法下面返回正确的 ASLayoutSpec
即可。比如:GICPanel的代码。
@implementation GICPanel +(NSString *)gic_elementName{ return @"panel"; } -(id)init{ self = [super init]; self.automaticallyManagesSubnodes = YES; return self; } -(ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{ ASAbsoluteLayoutSpec *absoluteSpec = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:self.gic_displayNodes]; return absoluteSpec; } @end 复制代码
从代码上来说,还是比较简单的。
而另外一个值得一提的是 GICGridPanel
这个面板是 GIC
自定义的一个面板,而非 Texture
提供的。有兴趣的可以看下 源码
二、对ASDisplayNode封装
GIC
中的UI元素都是直接或者间接继承自 ASDisplayNode
, Texture
本身提供了多种基本UI元素,比如: ASTextNode
、 ASNetworkImageNode
、 ASScrollNode
、 ASTableNode
等等,而对这些基础Node的组合应用基本能够应付大多数需求,如果这些基础Node无法提供你所需的功能,您也能通过自定义的方式创建符合你需求的Node,在 GIC
中,布局面板就是一个很好的例子。
而 GIC
对 ASDisplayNode
的封装,其实就是对各种基础Node的二次封装的过程,拿 image
元素举例来说, image
其实就是继承自 ASNetworkImageNode
的自定义元素( 源码 ),只不过就是增加了属性而已,而其他的UI元素也是这么做的,在前面一篇博客中已经提到,这些UI元素都算做是 GIC
自定义元素的范畴。
三、对ASTableNode以及ASCollectionNode的封装。
GIC
中的 list
元素其实也是一个对 ASTableNode
二次封装的自定义元素。之所以这里要单独提出来说,那是因为 GIC
在 ASTableNode
的基础上做了进一步的优化。
基本的封装流程其实跟平常的 UITableView
的开发流程差不多,从 Header
、 Footer
、 Section
、 UITableViewCell
等等 UITableView
的特性一样不落,所有的 list-item(UITableViewCell)
必须被包含在 section
中,一个 list
至少需要一个 section
,每个 section
可以包含独立的 header
、 footer
,在使用XML写 list
的时候你会发现开发流程跟 UITableView
的开发流程差不多,只是你现在只需要使用XML就能实现 UITableView
的功能。
基本的封装相对来说还是比较简单的,但是 list
花了很大的精力在另外一个优化上,那就是一次性大量数据加载的优化上。
举一个例子,一个列表需要一次性加载100条数据,也就是说需要创建100个Cell,而且那些Cell的布局还是比较复杂的(先不要问“为什么需要一次性加载100条数据,不会分批加载吗?”)。如果不加优化一股脑的直接全部插入 ASTableNode
,那么你会发现要经过好几秒才能显示出来,这并不是说 ASTableNode
性能不行才需要耗费如此长时间, ASTableNode
已经启用了异步加载,但是要创建100个Cell也是要消耗很大的CPU资源的,虽然是在非UI线程上创建的,但是需要消耗的CPU资源是跑不掉的,再说了,最终能够显示出来的视图还必须是UIView,哪怕在极端情况下,一次性在UI线程创建100个UIView也是能让UI线程够喝一壶的了!因此这并不是 ASTableNode
的锅,这锅应该开发者背,不管是用 UITableView
还是 ASTableNode
,一次性加载100多条数据甚至更多,本来就是有问题的。哪怕实际的需求确实是要求一次性加载100条,但是我们难道在实现的时候不能人为分批加载吗?
GIC
正是基于这样的思路提供了一个通用的解决方案,直接将此功能封装到了 list
和 collection-view
两个元素中。
下面主要着重分享下这个解决方案。
首先是如何分批加载,因为当设置数据源的时候,肯定是一次性来100条数据的,那么如何将这100条数据分成10批加载(假设每次加载10条数据),要是有一个方法能够自动将100条数据按照每次处理10条数据方式来处理所有数据就好了,然而实际的情况可能更复杂,也许当你100条数据还未全部加载完毕,又插入了一批新的数据,又因为 GIC
是支持异步加载的,也即是意味着新插入的数据是可以在任何线程上插入的,而且插入的数据有可能并不是一次性加入而是有可能是一条一条插入的(事实上, GIC
中的for指令在插入数据的时候就是一条一条插入的),这又极大的增加了开发难度。这时候就想着,有没有什么方法可以使得在特定的时间内将所有插入的数据都先放到一边,然后按照每次处理10条数据的方式来处理数据,这时候想到了类似 RXJava
、 RXJS
中的 buffer
方法,而 RAC
只提供了 bufferWithTime
的方法,因此 GIC
就需要在这个方法上做进一步的处理。
bufferWithTime
的作用就是在给定的时间内所有被发射的数据都会被打包到一个数组中,等时间到了再一次性的将整个数组发射出来。如下图:
GIC
利用了这个方法的特性,先将0.05秒内的所有的数据全部打包到一起,再一次性发射出来,这样就确保了0.05秒内的所有数据都能在超时后得到处理,并且统一调度到UI线程进行插入操作。也就是说在数据源层面,你尽管往数据源中添加数据,然后由 bufferTime
再统一添加到一个单独的数组中,然后等待(0.05秒)超时后再一次性的在UI线程上发射出来。然后进入下一个处理逻辑。
首先是判断当前 ASTableNode
是否处于处理数据状态(可以通过 ASTableNode
的 isProcessingUpdates
属性来判断),如果处于 Processing
状态,那么将这批数据再重新放回 bufferTime
中,等待下一次的超时处理。而如果当前 ASTableNode
并不是处于 Processing
状态,那么就将这批数据按照每批10条数据的方式分批处理(这里用到了递归方式来处理),当每次10条数据处理完毕后再递归处理下一个10条数据,直到所有的数据都处理完成。
这样的处理方式不仅保证了在加载大量数据的时候能够确保准确插入,也保证了首屏数据的显示速度。事实上,按照每次处理10条数据的逻辑来看,也就是说首屏显示出来的数据是10条数据,这样不管一次性加载多少数据,首屏的10条数据都会在短时间内加载完毕并且显示出来,这样在用户体验上将会得到一个极大的提升,哪怕这时候后台其实还在处理剩余的数据,但是对于用户来说这个过程是无感知的。用户只会看到数据很快就显示出来了。
其实这样的处理方式并不是专门为 ASTableNode
提供的,任何我们平常开发 UITableView
的时候都可以使用这样的解决方案来提高首屏显示速度。而在实际的测试结果来看,从数据加载到首屏显示(10条数据)的时间差不多是60毫秒(iphone6),这个时间包含了 XML解析
、 数据绑定
、 自动布局计算
、 渲染
、 显示
这一整个流程,可以说是非常的快速了。即是是在iphone4那样的设备上,也能在90毫秒内完成。(这里的时间测试结果,布局参照对象为开篇提到的那个设计)。
我试过直接使用常规的开发方式直接使用 UITableView
来开发,同样是显示10条数据,从拿到数据到最终显示完毕,在iphone6上差不多是200毫秒。当然这里面并未经过太多的性能优化,也许经过优化过后能够争取在150毫秒甚至更短的时间内完成,但是这样的比较意义在于,当你使用 GIC
后,你压根就无需去考虑性能优化的问题,并且无需进行复杂的架构设计,同样能够在极短的时间内将内容显示出来。
注:如何测试 UITableView
从拿到数据到显示的时间?
CGFloat *d1 = [[NSDate date] timeIntervalSince1970]; // 处理数据,比如计算cell的高度 dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; dispatch_async(dispatch_get_main_queue(), ^{ CGFloat *d2 = [[NSDate date] timeIntervalSince1970]; // d2-d1 就能得到从拿到数据到显示完毕的时间差 }); }); 复制代码
最后
GIC
得益于 Texture
的高性能、强大的布局系统,使得在使用XML写传统的UI布局的时候不仅在开发效率上得到极大的提高,而且也直接提升了性能。哪怕您仅仅是拿 GIC
作为一种UI布局工具,也能极大的提升你的开发效率,而且 GIC
支持局部UI替换,也就是说你一个页面中部分UI使用 XML
来布局,部分UI还是使用原来的 coding
方式来布局,可以说非常的灵活。
更进一步,当你将业务逻辑使用 JavaScript
来写的时候,那么意味着整个APP具备了 热更新
的能力。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。