一些iOS的朋友们对xib的还有误解,有同学面试聊到布局方式的时候,可能会很熟悉一句话“我们不用xib”。这次应同事邀请,在工作之余,做了一些调研并做了一些简单测试,因为时间关系,挑一些重点疑惑和顾虑,做了简单的keynote跟同事们分享了,现在把keynote转化摘抄写成博客跟大家分享。
温馨提示:此博客可能会有强烈的主观色彩,请谨慎阅读
本次分享的内容如下:
零、事物是变化发展的原理及方法论要求
一、xib简述
二、xib的一些误解和争议
三、xib对ipa包大小的影响
四、xib对APP性能的影响
五、xib的可维护性(冲突解决及避免)
六、xib与masonry写法的比较
七、参考、推荐资料
xib问世到现在,有过争议,但是xib也是在不断变化发展的。
先贴一下马克思主义基本原理及方法论要求
零、事物是变化发展的原理及方法论要求
原理归纳:
唯物辩证法认为,世界上一切事物都处在永不停息的运动、变化和发展的过程中,整个世界是一个无限变化和永恒发展着的物质世界。
发展就是新事物的产生和旧事物的灭亡,即新事物代替旧事物的过程。
方法论要求
1、要求我们坚持用发展的眼光看问题
2、要把事物如实地看成是一个变化发展的过程
3、要弄清事物在发展过程中所处的阶段和地位
4、要与时俱进,培养创新精神,促进新事物的成长
(反对形而上学的孤立的、静止的、片面的看问题)
附:
真理是具体的
任何真理都是相对于特定的过程来说的,都是主观与客观、理论与实践的具体的历史的统一。
真理是有条件的
任何真理都有自己的适用条件和范围,如果超出这个条件和范围,只要再多走一步,哪怕同一方向迈出一小步,真理就会变成谬误。
1.从认识的主体看:
人们的认识总要受到具体的实践水平的限制,还会受到不同立场、观点、方法、知识水平、思维能力、生理因素等条件的限制。
2.从认识的客体看:
客观事物是复杂变化的,其本质的暴露和展现有一个过程。
一、xib简述
1、什么是xib
ib: Interface Builder 即图形界面设计
nib:这个名字来自于NextStep 系统,在NextStep被Apple收购之前,一直使用nib作为Interface Builder的图形文档。nib的发展经过了nib2.0,nib3.0,到NextStep被Apple收购之后,nib发展到了xib。
xib是一个XML格式的纯文本文件,而此时的nib是一个二进制文件。xib编译完以后最终还是得到nib。
Xcode.3.0 以后可以使用xib,Xcode 4.0 开始 interface Builder 被直接集成到 Xcode。
下面我先来看看xib两种存在形式,即interface builder形式和source code形式,如下图:
最后,还有打完包后编译得到的nib。
终上所述,可以概括:xib就是用XML描述的ib。
那么,为什么要采用XML来描述IB?
我认为,原因有二:
1、XML的纯文本文件比二进制文件更方便进行diff操作,可读性更好。
2、XML格式化良好(通过 DTD 验证、约束良好);
(未见xib里面有DTD,应该是ib工具内置了,不需要显示的编写,否则代码就不会出现打不开的情况了)
那么,再问,xib会不会发展成为jib(JSON interface Builder)、pbib(Protocol Buffer interface Builder)等等?
根据上述两条,我认为不会。而之所以JSON、Protocol Buffer其他很多地方能取代xml,是因为他们简单的数据组织结构,尤其是在数据传输的的过程中有明显优势。
二、xib的一些误解和争议
转做iOS开发以来,已经小五年了,和很多前辈一样,经历过计算Frame位置及大小的岁月,用过一些的第三方布局库,还有直接撸起袖子硬编NSLayoutConstraint,还玩过“H:|-[view1]-[view2]-(>=15)-[view3]-|”,也积极勇敢的抱着“苹果爸爸(好像大家都喜欢这么叫,那就别问问什么了☺️)”的大腿坚持xib,也用过storyboard,领略过各路大神的风骚,也给继任者留下过一堆holy shit的代码,也被填过坑,坑里还有钉♀️。
stop,扯的有点远,言归正传。
嗯,看了一些博客,其中的一些论据和观点已经过时。因为很多博主在进行实践总结过程中的有些软件环境、软件环境没有进行说明,又或者即使进行说明了,但其难以复现或已不复存在,因此在此,我本人愿意相信各位博主当时的实践结果和结论观点是历史正确的。但是在此,如果与当下实践不符的,包括结果、论据和以此而产生的观点,本人将将其视为一种误解,不再对其历史的真伪做辨析。同时也在此像各位博主致以崇高的敬意,感谢在百忙之中抽空记录、总结并无私分享你们的宝贵经验。
下面我们来看看一些误解和争议的例子(A:表示我的回答;B:表示我提出的问题):
1.我们发现 xib 中设置的颜色值并不精确,RGB 在真机 / 模拟器上常常会有 10 多像素的偏差
A:其实是hexColor转换成RGB转换的时候精度丢失了,如果要是按照UI的标注在xib中设置hexColor与在程序中设置RGB相比,真正有色值偏差的是RGB,而不是xib中设置的hexColor。另外“像素”指的是色素,色值吧。
2.xib 和 storyboard 对继承的支持并不友好。无法做界面的继承
Q:view为什么还要做继承?view里面牵扯到布局了,问,指的是继承其属性(界面元素)然后在重新布局吗?如果是这样,那么这么做的好处与所见即所得的可维护性和可操作性哪个成本更低?
3.storyboard 对组合支持得不太好,不允许在一个 xib 中附带多个子 view
A&Q:一个view的布局精简最好,一个页面集成了那么多子view维护起来非常不方便,如果两个页面同时使用、集成多个子view的viewA,现在有这样的需求,需要改变一个页面中viewA的布局方式,请问那是重写呢,还是对viewA里的参数子view做特殊处理?如果更多使用这样的viewA呢?(继承也会有类似问题)
4.xib 和 storyboard 对搜索支持并不友好,无法方便地在 Xcode 中查找关键词(但是可以通过写 bash 命令来查找)
Q&A:这里的关键词是指的什么?比如说是控件上的文字?还是控件对应的class?如果说的是这些,事实上是支持的
5.xib 和 storyboard 不太方便做界面的模块化管理,比如我们想统一修改界面中所有按钮的字体样式,那么在 xib 和 storyboard 只能一个一个手工修改,而如果是代码编写的,则只需要改一个工厂方法的实现即可
A:xib的控件元素是可以指定具体的定制类的,和手写代码不误二致。
6.一些其他争议
a、XIB载入相比纯代码自然要慢一些,而且打出来的包会大一些;
b、需求变动时,XIB有时需要改动很大,甚至需要重新添加约束,导致开发周期变长;
c、对于比较复杂布局和逻辑控制,使用XIB是比较困难的;
d、当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突极难。
下面我们围绕上述第6条的争议展开看一下。
三、xib对ipa包大小的影响
写了两个简单的项目,实现一个简单的页面,一个是用masonry写的,一个是xib写的。为了区别尽量小,两个项目里都同时引入了masonry库,虽然xib那份没有用到,但是还是一起引入了。
源码地址:SingleViewAppWithMassonry、SingleViewAppWithXib
下图是各项目所占用的磁盘空间:
下图是打包后的release包所占的磁盘空间:
两个项目的区别是,其中的一个tableViewCell和一个viewcController的实现用masonry实现,一个使用xib实现。
测试的结果看,SingleViewAppWithMassonry 的所需磁盘空间要小于 SingleViewAppWithXib 9KB。而从 release 包 打包的结果看 SingleViewAppWithMassonry 的磁盘空间要大于SingleViewAppWithXib 22KB。
而我们的商业版 app 里会有多少个 tableViewCell 呢?
备注:xcode版本应该是打包时间2018年05月29日当时最新版本,后面自动跟新了,没有记录当时的版本,和现在的现在的版本差别应该不大(大家可以自己打个包看看)。
四、xib对APP性能的影响
说说道 iOS 性能指标,我们一般会通常考虑以下指标:
a、资源消耗
b、内存泄漏
c、流量消耗
d、耗电功率
e、渲染效果
f、加载时间
在此我主要考察说明第e、第f条。其他的在貌似没有多大争议,基本上差不多,“我自己分析认为”影响不大。至于第e条,渲染效果肉眼我是看不出区别来。有个FPS的指标,我试了,感觉人为的因素比较大。在此不做考虑。
下面主要考察说明加载时间。
在此之前,我们先来了解一下UINib这个类(在此建议,如果不想人云亦云,那么官网一定要看,而且非看不可;这个等式大家应该很熟悉了:0.9 * 0.9 *0.9 * 0.9 * 0.9 = 0.59049)。
1、UINib
下面是官网截图:
我就先试着翻译一下,水平有限,我就试试看
a、An object that wraps, or contains, Interface Builder nib files.
(UINib是这样一个对象(类对象),包裹或者说是包含着这些用于图形界面设计的nib文件)
b、A UINib objectcaches the contents of a nib file in memory, ready for unarchiving and instantiation. When your application needs to instantiate the contents of the nib file it can do so without having to load the data from the nib file first, improving performance.
(UINib对象在内存中缓存了nib文件的内容,以备解档和实例化。当应用程序要实例化这些nib文件描述的界面元素的时候,就没有必要首先要从nib文件加载数据了,这样就提高了性能)
c、The UINib object can automatically release this cached nib data to free up memory for your application under low-memory conditions, reloading that data the next time your application instantiates the nib.
(当应用程序处于低内存的情况下,UINib对象会自动释放其缓存的nib数据,释放内存,而在下一次实例化这个nib时,重新加载数据。)
d、Your application should use UINib objects whenever it needs to repeatedly instantiate the same nib data. ( For example, if your table view uses a nib file to instantiate table view cells, caching the nib in a UINib object can provide a significant performance improvement.)
(无论何时,只要你需要重复使用同样的nib数据,你的应用程序就应该使用UINib对象。(比如:如果 table view 用 nib 文件来实例化 table view cells,把 nib 缓存在 UINib 对象里,会有一个重要的、巨大的性能提升。))
e、When you create a UINib object using the contents of a nib file, the object loads the object graph in the referenced nib file,but it does not yet unarchive it.
(当你使用nib文件的内容创建UINib对象时,UINib对象只是加载了其相关nib文件描述的对象的刻画,到目前为止并没有解档这些对象)
到此UINib告段落,下面我们继续了解xib的加载过程。
2、xib文件的加载过程
a、xib文件从磁盘载入内存(有两种技术可以加载xib文件:NSBundle和UINib)。
b、执行unarchive和initialize操作,该过程主要由NSCoding Protocol中的initWithCoder:(NSCoder *)decoder完成。
c、建立connections(Outlets和Actions ):使用setValue:forKey:方法建立每个Outlet,建立时会发送KVO通知;通过调用addTarget:action:forControlEvents:方法建立每个Action连接。
d、调用awakeFromNib方法。( 此时可以开始编码 )
下图是nib的加载过程,我们主要关注红框里的内容:
看到这里可能很多朋友会想到 KVC 的实现机制,认为正常的 access(getter / setter) method,会比KVC快,因为KVC在键值查找过程中的兼容实现会多发几个消息或者多做一些处理。
其实大家想的是对的。但是,具体慢多少?怎么个慢法?是不是性能(加载时间)瓶颈所在?我可以来做做实验,比较一下,KVC 存取方法与正常的存取方法耗时。
3、access method 之 KVC VS Normal
下面是我做了很多次试验的两次代表,直接看图
其中测试环境如下:
软件环境:
Xcode Version 9.4.1
iOS Version 11.2.1
硬件环境:
iPhone SE
从测试的结果可以看到:
a、确实,Normal 的 access 方法比 KVC 的 access 方法快
b、多个对象和同一个对象:getter快10到15倍;setter快10到20倍
5万次的存取中最高的一次耗时是0.009527秒,约等于0.00000019054s/次,约等于0.19微秒/次
而大多数人认为,人类的反应时间为最短为0.1s;(据说刘翔反应0.139秒,而我没有参与测试,也不知道我的是不是0.1s ?)
所以,虽然慢,但是带来的影响可以忽略不计。
下面再来看看 viewController中 xib 与masonry的加载速度。
4、viewController中 xib 与masonry 的加载速度
源码在这,先看实现的效果图
下面是控制台输出的耗时日志截图:
比较发现,每次 push 耗时都不一样,而且先 push 谁,开始第一次耗时是比较多的,具体原因不展开讲了。为了防止偶然性带来的误差,我做了很多次 push 操作,在此取其中一次做的20次 push 做统计说明。
其中测试环境如下:
软件环境:
Xcode Version 9.4.1
iOS Version 11.1.1
硬件环境:
iPhone 8
从测试的结果的到:
随机看:xib比masonry不相上下,
统计来看:xib比masonry略快(平均,毫秒级)
结论:加载速度差别不大(记住我的那0.1秒,在此我是以 viewDidAppear 为加载完成标志)
五、xib的可维护性(冲突解决及避免)
1、可维护性角度(微观)
这里讨论的是微观的可维护性。主要有以下几个指标:
a、理解;
b、修改;
c、重写;
d、代码冲突的解决
即,xib容不容易理解?好不好修改?修改不了,或者说需求改动很大,直接重写成本有多大?还有,代码冲突了怎么办,是不是真的不好解决?如果解决不了了,还得重写。
我们先看看几个例子(请同时关注两边情况)
注意看表情!
碰到这么写 xib 的我也害怕,而且非常害怕。
这样的xib会存在什么问题?
我发现上面提到的观点竟然基本上是对的
a、XIB载入相比纯代码自然要慢一些,而且打出来的包会大一些;❎
b、需求变动时,XIB有时需要改动很大,甚至需要重新添加约束,导致开发周期变长;✅
c、对于比较复杂布局和逻辑控制,使用XIB是比较困难的;✅
d、当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突极难。✅
如果按照上面例子的写法写xib,那么碰到以下的页面结果会怎样?
还是注意表情
结果是:
程序员累死,架构师气死
那么,该怎么解决?
我就不信!用车的、修车的能比造车的,更了解车!!!
看看官网怎么说 Resource Programming Guide
When creating your nib files, it is important to think carefully about how you intend to use the objects in that file. A very simple application might be able to store all of its user interface components in a single nib file, but for most applications, it is better to distribute components across multiple nib files. Creating smaller nib files lets you load only those portions of your interface that you need immediately. They also make it easier to debug any problems you might encounter, since there are fewer places to look for problems.
大概的意思是说在你Interface Builder的时候,最好是把界面元素合理的分布到多个nib file, 而不是把它全都放到一个大大的huge nib file。分布到多个文件的好处是,不仅提高加载效率, 而且bug定位更容易。
看看实践的效果
将此页面分解成不同的cell后是这样的
如上述图示,整个复杂的页面就被差分成了很多小的灵活的布局单元。
再来看看之前提到过的问题。
a、XIB载入相比纯代码自然要慢一些,而且打出来的包会大一些;❎
b、需求变动时,XIB有时需要改动很大,甚至需要重新添加约束,导致开发周期变长;❎
c、对于比较复杂布局和逻辑控制,使用XIB是比较困难的;❎d、当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突极难。✅
接下来我们再来看看第d个问题。
2、xib的代码冲突
这里先提一个问题,什么时候会有代码冲突?
一句话概括:不同分支对同一非忽略代码块的不同改动。(这里用的gitLab做版本控制)
即代码冲突有四个条件,缺一不可:
0、非忽略文件
1、不同分支
2、同一代码块
3、不同改动
注意这里的代码块是开放性的、或者说是广义的。就像多人组成一组,一人也可以是一组。而这里的代码块的最小组成单位是行(一行代码),多行组成一个代码块,一行是也可以是一个代码块。
举个例子,如下图所示,不同分支对不同非忽略代码块的不同修改,是不会造成冲突的。
但是很遗憾,xib可视化控件,和其背后的xml代码不太一样。
看下图,我们想在现有的布局里,在红框位置加上两个按钮。不同分支分别添加一个,然后合并,看看有什么问题。
sourceTree的两个不同分支如下图:
代码合并时有冲突如下图:
这时候,Xcode可视化编辑界面是打不开的,如下图:
以上就是xib代码合并时冲突的情况。怎么解决?
代码冲突,目前来看,解决无非三种
a、参照自己的( local )
b、参照外来的( develop )
c、和谐共融的( (* ̄~ ̄) )
对于前面两种,非常简单,如果下图所示,使用“我的的版本”或者使用“他人版本”解决冲突即可。
第c种,也很简单(视频还在masking中);需要先花几分钟仔细看一个正常的 xib 的 source code,你会发现 xib 很简单。再看看这种冲突其实跟“代码”一样。
解决冲突冲突后,和代码的现实也是一样的,如下图:
还记得,早年前我们点击一个下xib文件,Xcode会自动给我们加一些代码,如版本号改变、兼容等内容,容易导致冲突,具体的是在哪个位置我记不清了。在这我试着还原一下,尽管可能有不对的地方,但是已经不重要了,因为发展到现在,这种情况基本上没有出现了。
那些年,Xcode版本,引起的常规冲突区域
记得应该是红框区域会因为版本号起冲突
说到这,对于xib,我们再看看他们的难易程度
1、理解✅;
2、修改✅;
3、重写✅;
4、代码冲突的解决及避免。
刚刚我们说了冲突的解决,下面我吗讨论冲突的避免。
核泄漏危害大
但是,不要害怕
我们还是可以尽量避免的嘛
那我们的代码冲突怎么避免?
还是回到刚刚的提出的问题:什么时候会有代码冲突?
答:不同分支对同一非忽略代码块的不同改动。
四个缺一不可的条件
0、非忽略文件
1、不同分支
2、同一代码块
3、不同改动
这里我们只能选择“不同分支”这个条件下手。而落实到现实开发中,基本上是一个人多个分支,一个分支只有一个人负责。在这里我们可以近似把分支等等价于一个负责人。这就要求“要求我们明确分工,各司其职,各自负责自己的模块,至少代码提交之前是这样的。” 当然一直以来我们就是这么做的。
至此,我们再看看xib的可维护性角度(微观)
1、理解✅;
2、修改✅;
3、重写✅;
4、代码冲突的解决及避免✅。
到这,我们可以愉快的愉快的玩耍了。
另外,我是一个比较懒而且害怕单调无聊的人,复杂的、大量的劳动我不怕;大量的、重复的又没有技术含量的劳动才是我最害怕的、最厌恶的。大家除了懒,估计和我也一样。
下面我们再来看看 xib 与 masonry 写法的比较。
下面是实现的效果图(也就是我们前面说的那两个项目):
先看 masonry 我自己写的,个人认为是还算简洁。如有提高的地方还请不吝赐教。
.m文件,总共187行
下面的是 xib 写法:
感觉怎么样?
我先说说我的看法。
xib与masonry的比较优势(一方优势就是另一方劣势)
xib比较优势
可视化,所见即所得,层次结构清晰
布局与逻辑分离,省去懒加载等大篇幅代码
约束可以编码(包括动画,在这里贴出来主要是很多朋友认为xib不能做复杂的动画,动态改变约束等)
masonry比较优势
动态的精细化布局
所以在此,我建议:用 Masonry 解决最后一公里
到此我我的分享就告段落了。
以下是一些参考资料。期间还查阅过一些资料,但是时间有点长了,有时间就写一点,当时也没想着会跟大家分享,所以没有记录具体的资料出处,现在已经记不清了。在此对相关资料的生产者表抱歉,谢谢。
参考资料
1、xib争议:http://blog.devtang.com/2015/03/22/ios-dev-controversy-2/
2、Ray Wenderlich吧地址(第一个回答者里):https://www.zhihu.com/question/26425301
3、Resource Programming Guide
特别推荐:
1、iOS界面布局,代码还是IB?总有一款适合你:https://www.jianshu.com/p/d624dd344c87
2、Resource Programming Guide:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6
3、UINib