为什么我比Interface Builder更喜欢代码

本文 最初发布在我的博客上

像今天许多iOS(和macOS)开发人员一样,我通过遵循Apple自己的教程开始为Apple设备进行开发。 我仍然记得,在2011年并且来自Python和C ++的背景下,在助手编辑器上打开.xib文件和相应的类,然后从一个文件拖到另一个文件以创建IBOutletsIBActions ,是多么神奇。 我想,“太好了”。 摆脱样板代码而只关注我的应用程序应该做的事情确实令人耳目一新。

没多久,我开始看到依靠这种矛盾的缺点。 特别是当我从从事小型教育项目转为开始与其他开发人员进行大型项目时。

合并地狱

.xib情节提要板都不是人类可读的。 您应该使用Xcode为您生成的那些文件的漂亮图形表示。 这意味着,如果您和您的同事碰巧在同一个文件上进行工作,那么其中一个将不得不在以后解决合并冲突。 很多时候,合并将涉及随机选择更改,然后在Xcode上打开文件并弄清楚需要做什么以确保文件处于所需状态。

最后但并非最不重要的一点:Xcode只需打开它们即可更改Interface Builder文件。 有时,稍加修改就会导致文件的大量更改(这是因为文件的XML节点在内存中表示为一个集合 ,而集合不能保证顺序)。 其他时候,Xcode只是为了好玩而更改.xib的语法。

紧密耦合,几乎没有编译时间安全

您是否曾经运行过您的应用程序,并确信它会按预期运行,只是在您打开某个视图控制器后看到它崩溃了? 在检查了崩溃的确切原因之后,发现这是未连接的IBOutlet吗? 是的,我们都去过那里。

此外,Interface Builder文件更难重用,甚至更难重构。 几乎不可能单看这样的文件并对文件定义的所有内容都一清二楚。

当涉及到Interface Builder的问题时,我几乎看到了所有事情。 从可能突然使Xcode崩溃的文件仅从打开某个文件到可以使用不同xib实例化的视图控制器开始,并且当使用这些xib中的一个实例化实例时,可能导致崩溃,因为等待它,因为IBOutlet没有该xib上的对应元素,并且由于Xcode生成IBOutlets作为隐式解包的可选变量,因此任何尝试访问该变量的尝试都会导致应用崩溃。

拖拉东西不是我的强项

有一些小事情使通过“界面生成器”添加约束的任务变得异常艰巨。 就像必须移动到下一个布局约束字段一样,以便当我单击“ 添加约束” ,Xcode打开单个Storyboard所花费的时间,或者正在编辑某些内容并使Xcode崩溃时,Xcode不会忽略我的约束。 (然后必须重新打开该项目,并找出最后保存的更改是什么)。

当用代码完成UI工作时,更容易准确地查明发生了什么或需要更改一年前编写的代码中要实现该新功能的内容。 特别是,如果代码是出于良好实践的考虑而编写的,那么所有内容都是明确的,我将确切地知道在哪里查找。 这使我想起了我使用Python多年的乐趣:显式比隐式好。

但是编写自动布局代码是如此冗长而乏味……

有一些框架可以消除编写自动布局代码的痛苦。 我选择的框架是制图学。 制图使编写自动布局代码极其简单和直观。 只需从项目的自述文件中查看以下示例:

 constrain(view) { view in 
view.width == 100
view.height == 100
}

let group = ConstraintGroup()

// Attach `view` to the top left corner of its superview
constrain(view, replace: group) { view in
view.top == view.superview!.top
view.left == view.superview!.left
}

/* Later */

// Move the view to the bottom right corner of its superview
constrain(view, replace: group) { view in
view.bottom == view.superview!.bottom
view.right == view.superview!.right
}

UIView.animateWithDuration(0.5, animations: view.layoutIfNeeded)

没有比这更简单的了。 另外,能够看到并快速了解我的同事在代码审查期间所做的一切,总是很高兴的:

你看见了吗? 常数! 甜蜜的常数!

在某些情况下,您总是不知如何使用自动布局来实现某些功能。 对于那些情况,我可以告诉你:覆盖layoutSubviews并在那里计算帧没有错。 永远不要让别人告诉你。 始终支持最简单的问题解决方案。