前言 |
坦白来说,我是storyboard / xib
重度使用患者。因为当时在学习Cocoa Touch
组件时候,被这种拖控件拽关联的“所见即所得”的界面搭建方式所深深吸引了,从此便入了iOS
开发的坑。颇有种“一入传销深似海”的感觉,(⊙﹏⊙)b!
了解MacOS
发展历史的朋友都知道,storyboard / xib
在这其中绝对是浓墨重彩的一笔,因为它颠覆了coder
对搭建UI
界面的认知,也确确实实带来了很多便利。然,随着技术的进步,coder
对它们的使用越发趋于理性,因为它的一些众所周知的弊病:
神一般
的git
冲突)相信有这样论点的文章你可以轻易搜索到,笔者目前并没有足够多的资历来论述这些冲突点,不过还是建议你看一看喵神(王巍 @onevcat)的一些看法。所以你可能就很难在大型的多人合作开发项目中看到它们的身影了,即使有的话,估计也会被告知为:历史包袱,不好改!(如果你的老板愿意让你花时间去做这些吃力却又不怎么讨好的事情就又另当别论了,^_^)
在第一份实习工作中,我就在一次中午下班吃饭的路上,和公司里的同事“倾述”看frame
布局代码的不适感(它的性能比AutoLayout
要好一些,不过要上了一定量级的布局设置才会体现得较为明显,因为我们都相信它在写一个登录界面会有不错的表现!),并询问着一些几乎不怎么变动的界面是否可以考虑用storyboard / xib
来写呢。这时,旁边Android
组组长鄙视地看着我说:“现在谁还用这种可视化搭建界面的工具啊!”
感到有点遗憾的是,现在storyboard / xib
逐渐沦为快速搭建Demo UI
的好帮手,成为学习新控件的好助手了。(笔者窃以为在Apple
给出以上提到的几点问题的解决方案之前,这种形势会维持下去。)正所谓时势造英雄,没有前车之鉴,就没有后事之师了!如果Apple
想继续维持它的影响力的话,这是亟待解决的问题!
???这和我有啥关系,[手动滑鸡] ~~~~
一两点 try |
我是在适配iPhone X
的过程中深刻意识到了用storyboard / xib
搭建界面可维护性差的问题,特别是看到那一堆层层叠叠的UI
元素的时候。因为它的制造者有在这里面添加约束很难维护的意识,但是又想享受不写控件初始化代码的便利。(至少对他来说是这样的)如果这些UI
元素有IBOutlet
、规范命名、文档注释、顺序排序(几乎没看到后两者满足的情况),我就不会看着Reveal
那么急躁地抓头发、抓头发、抓头发了!
至此,我也成为了使用代码写界面的拥护者。其实我们都知道,在SB
里面拖拽的元素几乎都需要二次加工,比如UILabel
显示用户名、获取用户输入的密码等等。就更别说适配iPad
而要做的那些工作了!它们可以很好的体现在控件通过代码创建的过程中,那么用SB
呢?相信你更愿意编写或者维护这样的代码:
#pragma mark - XX or MARK: - XX
)IBAction
、IBOutlet
之类的元素(因为当你想理解更多的时候,你不得不再次切换文件,并感受打开storyboard / xib
文件的延迟,甚至一定几率的卡死)虽然我们的代码最直接的面向者是计算机,即希望它能够按照我们制定的程序运转。但是这只是coder
的基本功,因为代码更多是给人看。如果有人问我:在工作中做过的最有成就感的事是什么?我会回答两点:一是真正比较难解决的技术难题,另一个就是写出了高质量、易维护的程序代码(你肯定得举出一些例子来说明)。(现目前还差得远,不过已经慢慢在这条路上走着了)
勒紧 你的裤腰带 |
无论是用SB
还是纯代码,在开发UI
界面的时候就肯定会与frame
、autoLayout
打交道。虽然我们可能会吐槽这两种愚蠢的布局方式而羡慕Android
更高性能的XML
,但无可奈何地说:我们必须用它们!得益于开源社区的无私贡献,我们可以使用像Masonry(Objective-C)
、SnapKit(Swift)
这些优秀的布局框架来减轻使用原生给我们带来的郁闷。这时就会涉及到一个问题:约束代码写在哪里?
首先,坚决抵制把约束代码写在控件的懒加载里面。在OC
中对应于getter
,在Swift
中对应于被lazy
修饰的属性。它们的职责是初始化控件,就属性本身的意义来说,它并不知道和它平级的其他属性的存在(就更别谈它的约束信息还要和别的属性相互依赖)。它只需要弄清楚谁是它的拥有者,并在其需要时完成自身的初始化。
然后,就笔者所了解到的,常见的两个位置是viewDidLoad
、viewWillLayoutSubviews
,下面贴出官方文档的截图:
可以看出Apple
建议我们在updateViewConstraints
里面添加约束,但是stackoverFlow
上有回答者说这里面可能会出现问题,至于是什么问题,我是处于懵圈状态。
对于上面提到的两个方法,更多的会放在前者里面。因为该方法只会在控制器的生命周期里(只要self.view
不会被强行置为nil
)调用一次,可以避免约束的重复添加。可以预见的是有些时候我们需要根据用户的行为改变UI
的布局,此时调用updateConstraints
,控制器就会调用viewWillLayoutSubviews
来更新约束。所以对于后者的担忧就是不需要变更的约束被重复添加,如果最初的约束代码是放在这里的话。但是方法的调用就一定意味着里面的约束代码一定会被执行吗?特别是对于那些不需要改变约束的控件来说?
可以使用一个简单的技术手段来跟踪验证一番:KVO
。我们都知道KVO
是不会对数据做去重操作的,也就是说即使设置的新值和旧值一样,观察者依然会接收到回调。(如果你研究过KVO
的Runtime
实现的话,对于这点你应该不会觉得疑惑)
测试的结果不出人意料并且也是情理之中的:在viewWillLayoutSubviews
里面给界面元素添加相同的约束,实际上并没有起作用。因为我们对约束不变的元素并没有调用updateConstraints
方法,这里我们就可以削微猜测一下这里面可能会有的一点逻辑:在更新控件约束时,会先检查它是否有需要更新的Flag
。如果有,才根据新的约束信息来确定其布局位置。调用updateConstraints
可能就是一个立Flag
的过程,^_^!
到底懒不懒 |
这其实关乎于OC
中到底是使用成员变量还是属性来实例化对象(与Swift
就形式上会有所区别)。在使用后者的时候就可以通过懒加载的方式来延迟对象的创建,但是有一个争论的点是:懒加载是针对于那些在应用中可能会用到的对象而使用的一种手段,而那些从应用被打开(或者是跳转到某个界面)就存在的对象不应该使用懒加载。(如果你还想着getter
里面对象判空的时间消耗,那你就多虑了)
嗯,这个观点是以懒加载的架构意义作为出发点而提出来的,从理论上来说确实如此。但是理论终究是要用于实践的,我们先贯彻这个思想,看看在代码中会是什么样的表现。
先从使用角度来看。因为很多对象不在使用懒加载的范围内,此时你可能会倾向于使用命名为_XX
的成员变量来表示对象(当然你也可以继续使用属性),并通过调用一个setupUI
之类的方法来初始化这些成员变量。当然这里面可能还会有方法的嵌套调用,毕竟没人能忍受一个方法里面的实现有百余行。在访问它们的时候,就直接通过名称来,没有self.
修饰。(继续使用属性除外)
从维护角度来看。你会看到两种使用规范的对象:一个是_XX
,一个是self.XX
。如果此时你关心对象的创建过程的话,对于前者你不得不翻阅一下代码,找找这个家伙的初始化方法在哪里。对于后者你只需要command + 点击属性
就可以了,并且它的所属关系也表现的很明确,同时它的代码结构也会更清晰。
可以看到此时的论点就由懒加载过渡到是使用属性还是私用的成员变量了,因为无论是OC
还是Swift
只有属性才能完成懒加载。(这么说有点不恰当,因为你完全可以使用方法或者函数实现懒加载的机制)它们的使用区别其实对应于懒加载,这里就不再赘述。
使用属性有一个疑虑是增加了ipa
大小,因为多出了getter
和setter
方法。但是前者的消耗和在.m
文件中写私有方法来初始化对象几乎是一样的,所以可以优化的地方就在setter
上面了。解决的办法很简单,只需要在@implementation
里面用@dynamic
修饰一下只读属性就可以了。
不过此时需要注意的是getter
和setter
需要你自己实现,否则在运行时访问将导致程序崩溃,因为你已经告诉编译器这两个方法在运行时产生。其次就是_XX
变量也不会被生成,如果你直接访问它的话,会收到编译器的报错。
如果你真心觉得有必要的话,可以尝试一下,:-D
总结 |
相信文章题目提到的这四者中没有谁有决定性优势和劣势,不然我们就不会为此争论不休了。虽然有点打太极的感觉,但它们的使用真得根据实际情况来决定。比如说你需要做的项目非常赶工期,这是SB
就可以救你于水火之中了。因为公司现在的目标是尽快的抢占市场,需要快的拿出产品来。如果产品本身就是失败的,你代码写得再好对公司也毫无价值可言。
就我个人的观点来看,我们首先是“业务员”,然后才是coder
。代码可以不断迭代,而市场一旦错失,可能就不会再有了。因此我们也应该以平常心去看待之前的就你现在来看可能会觉得糟糕的代码,毕竟没有他们的糟糠在前,那么后面你的珠玉也就没机会展示了。