如何避免情节提要中的Segue标识符

Segue标识符是纯字符串 。 故事板中的属性和代码中的文字。 我们需要确保情节提要中的代码与代码匹配。

如果我们需要以编程方式调用segue,则它们很有用,因为它们可以识别segue。 但是通常, 标识符用于将信息从一个视图控制器传递到另一个视图控制器。 像在此示例中一样,标识符用于从源视图控制器A配置B或C:

运行时错误 。 标识符是字符串。 我们可能会拼写错误或重构代码并更改字符串,而不会更新对应的字符串。 这会导致运行时错误。

如果标识符错误,则将继续执行segue,并且目的地将无法正确准备。 准备目的地的代码永远不会执行。 编译器无法检查标识符是否匹配,因此对错误一无所知。 开发人员也不了解它们。 将显示新的视图控制器,并且该应用将崩溃或行为不正确。

最糟糕的部分是,如果我们犯同样的错误,即仅在一侧更改标识符,则会在修复它们后再次出现这些错误。

我们可以通过定义标识符的命名约定( 请参见 stackoverflow问题 )和/或使用常量或枚举而不是文字来尝试最小化此问题

注意:Natasha The Robot在文章“ Swift中面向协议的Segue标识符 ”中解释了使用协议和枚举的字符串文字的另一种方法 他们摆脱了文字字符串,提高了代码质量。 序列标识符仍被使用。

刚性和脆弱性 。 Segue标识符很难维护。 它们的变化会影响不同的部分,它们是刚性的 。 如果您改变一侧,则会破坏另一侧,它们很脆弱

注意:刚度和脆弱性是不良设计的标志,摘自 罗伯特·C·马丁(Robert C. Martin)的 依赖反转原理 ”。

重复的代码。 当我们重写方法prepare(for segue:UIStoryboardSegue, sender: Any?) ,我们将在条件prepare(for segue:UIStoryboardSegue, sender: Any?)和目标cast中复制代码 所有条件都是相同的,它们会检查segue ID。 还对每个源视图控制器重复进行强制转换。 如果多个源视图控制器需要转到B,则将所有这些源都配置为将B配置为目标的相同代码。

注意: sourcemaking.com 上的 一篇 不错的 文章,介绍了如何使用多态重构条件语句及其带来的好处。

不干净。 最后,这些条件和乏味的重复代码掩盖了设置目的地的实际工作。

我们可以摆脱标识符吗?

segue连接两个视图控制器,一个源和一个目标。 segue实例具有对它们的引用,它知道源实例和目标实例。 如果使用该信息,则不需要标识符即可将参数传递给目的地。 除非源在一个以上的序列中到达同一目的地(在这种情况下,标识符是必需的)。

删除Segue标识符。

我的方法使用了segue和某些协议的帮助。 它包括定义知道如何准备每个目的地的源协议。 通过使用由segue触发的机制,目标与源进行对话,以便可以将其配置回去。

怎么运行的?

步骤1.为每个目的地定义一个源协议。

步骤1.源协议

步骤2.遵循源代码中的那些协议。

步骤3.将准备工作委托给segue。 在覆盖的函数prepare(for segue:UIStoryboardSegue, sender: Any?)调用扩展名segue.prepare(sender:sender) prepare(for segue:UIStoryboardSegue, sender: Any?)

步骤2和步骤3。

步骤4.符合SegueDestination协议,在目标中实现prepareAsDestination(segue:UIStoryboardSegue, sender:Any?) 。 在这里,将源转换为源协议( SourceToBSourceToC )并调用协议方法。

步骤4.符合B中的SegueDestination
步骤4.符合C中的SegueDestination

在步骤1中,消息源将准备工作委托给segue。 UIStoryboardSegue扩展支持此功能。

UIStoryboardSegue扩展。

UIStoryboardSegue func prepare(sender:Any?)允许源视图控制器将准备工作委派给segue。 segue将目的地转换为SegueDestination如果您忘记遵守SegueDestination协议,则会抛出异常 。 然后,它将prepareAsDestination(segue:self, sender:sender)SegueDestination

在步骤3上调用它。

SegueDestination协议。

SegueDestination协议仅定义一个功能。 prepareAsDestination(segue:UIStoryboardSegue, sender:Any?)此功能是目标视图控制器在步骤4中需要重写的功能。 然后目标将转换源并调用适当的方法

在步骤4上遵守此协议。

而已。 现在,当用户点击按钮时,A与Segue对话,Segue告诉SegueDestination B / C准备,目的地与它的源对话,告诉A过渡到目标B / C。 不需要/不需要开关,不需要字符串。

需要标识符的情况

我必须更多地提到需要标识符的情况。 源视图控制器(A)可以选择连接到另一个视图控制器(D),以执行两个不同的操作,例如“视图”“编辑” 。 如果D的配置取决于这些操作, 则需要标识符

对于这种情况,方法不会改变。 我们为D创建源协议,并在A中遵循该协议。在D的准备中,我们检查标识符并决定将哪些信息发送给D。

当A在两个不同的阶段进入D时,标识符是必需的。

如前所述,我们可以使用命名约定和协议技术来改进此代码。 但是需要使用标识符。

最后的话。

我们可以传递信息,而不必使用segue标识符。 我认为这会导致更好的设计,但要写更多的代码。 但是代码更简洁,更不容易出错。

上面说明的潜在运行时错误消失了。 我们仅有的运行时错误不符合SegueDestination协议。 但是该错误很容易被捕获,因为它只出现一次。 如果我们忘记执行第4步,则会得到异常。 好消息是,此错误不会再次出现,就像带有标识符的错误一样。

这是对解决方案的简短分析:

缺点

  • 更多代码。 我们需要为每个目的地定义一个协议。 并在每个目标视图控制器中重写prepareAsDestination
  • 可能很难向新开发者解释。 我猜想动作序列比简单的if / switch更为复杂。

优点

  • 没有再次出现运行时错误。 最好在编译时失败,因为不符合协议。 正如我上面提到的,最坏的情况是获得异常,但是一旦我们第一次看到它,便实现了该协议,该问题将不再出现。
  • 清洁代码。 没有if语句。 每种准备工作都有其自己的方法。 目标定义它们的源协议,源实现它们。
  • 无需编辑/维护情节提要。 一切都在代码中,并在开发人员手中。 仅在源多次到达同一目的地的情况下,才需要定义序列标识符。
  • 打开/关闭原则。 如果添加新的目的地,则无需修改源上的开关,只需遵循协议并添加新的方法即可。

结论

  • 我宁愿编写一些多余的东西来摆脱字符串+ if / switch语句。 我认为支出是好的,主要是因为我们摆脱了潜在的运行时错误,并且代码更简洁。

下载 带有工作示例 XCode项目 仅凭 代码 获得 要点

leandromperez / segue-identifiers
segue-identifiers –一个小项目,用于显示segue标识符的替代方法 github.com