如何避免情节提要中的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.为每个目的地定义一个源协议。
步骤2.遵循源代码中的那些协议。
步骤3.将准备工作委托给segue。 在覆盖的函数prepare(for segue:UIStoryboardSegue, sender: Any?)
调用扩展名segue.prepare(sender:sender)
prepare(for segue:UIStoryboardSegue, sender: Any?)
步骤4.符合SegueDestination协议,在目标中实现prepareAsDestination(segue:UIStoryboardSegue, sender:Any?)
。 在这里,将源转换为源协议( SourceToB
或SourceToC
)并调用协议方法。
在步骤1中,消息源将准备工作委托给segue。 UIStoryboardSegue扩展支持此功能。
UIStoryboardSegue扩展。
UIStoryboardSegue func prepare(sender:Any?)
允许源视图控制器将准备工作委派给segue。 segue将目的地转换为SegueDestination
。 如果您忘记遵守SegueDestination协议,则会抛出异常 。 然后,它将prepareAsDestination(segue:self, sender:sender)
到SegueDestination
。
SegueDestination协议。
SegueDestination协议仅定义一个功能。 prepareAsDestination(segue:UIStoryboardSegue, sender:Any?)
此功能是目标视图控制器在步骤4中需要重写的功能。 然后目标将转换源并调用适当的方法
而已。 现在,当用户点击按钮时,A与Segue对话,Segue告诉SegueDestination B / C准备,目的地与它的源对话,告诉A过渡到目标B / C。 不需要/不需要开关,不需要字符串。
需要标识符的情况
我必须更多地提到需要标识符的情况。 源视图控制器(A)可以选择连接到另一个视图控制器(D),以执行两个不同的操作,例如“视图”或“编辑” 。 如果D的配置取决于这些操作, 则需要标识符 。
对于这种情况,方法不会改变。 我们为D创建源协议,并在A中遵循该协议。在D的准备中,我们检查标识符并决定将哪些信息发送给D。
如前所述,我们可以使用命名约定和协议技术来改进此代码。 但是需要使用标识符。
最后的话。
我们可以传递信息,而不必使用segue标识符。 我认为这会导致更好的设计,但要写更多的代码。 但是代码更简洁,更不容易出错。
上面说明的潜在运行时错误消失了。 我们仅有的运行时错误不符合SegueDestination协议。 但是该错误很容易被捕获,因为它只出现一次。 如果我们忘记执行第4步,则会得到异常。 好消息是,此错误不会再次出现,就像带有标识符的错误一样。
这是对解决方案的简短分析:
缺点
- 更多代码。 我们需要为每个目的地定义一个协议。 并在每个目标视图控制器中重写
prepareAsDestination
。 - 可能很难向新开发者解释。 我猜想动作序列比简单的if / switch更为复杂。
优点
- 没有再次出现运行时错误。 最好在编译时失败,因为不符合协议。 正如我上面提到的,最坏的情况是获得异常,但是一旦我们第一次看到它,便实现了该协议,该问题将不再出现。
- 清洁代码。 没有if语句。 每种准备工作都有其自己的方法。 目标定义它们的源协议,源实现它们。
- 无需编辑/维护情节提要。 一切都在代码中,并在开发人员手中。 仅在源多次到达同一目的地的情况下,才需要定义序列标识符。
- 打开/关闭原则。 如果添加新的目的地,则无需修改源上的开关,只需遵循协议并添加新的方法即可。
结论
- 我宁愿编写一些多余的东西来摆脱字符串+ if / switch语句。 我认为支出是好的,主要是因为我们摆脱了潜在的运行时错误,并且代码更简洁。
下载 带有工作示例 的 XCode项目 。 或 仅凭 代码 获得 要点
leandromperez / segue-identifiers
segue-identifiers –一个小项目,用于显示segue标识符的替代方法 github.com