内容简介:..
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具备了 热更新 的能力。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。