Swift中的编码

通过利用Swift的语言功能来创建和维护健壮的代码库。

因为您不想在承受提供功能的压力时发现并修复错误。 减少消防时间,增加创新时间。

不变性和可变性

健壮的代码可以很好地处理所有极端情况。 通过支持不变性,我们可以强制排除尽可能多的情况。 因此,使用let关键字声明的变量总是更有利的。 由于变量无法突变,因此我们消除了所有其他情况,从而使运行时的行为更易于推论。

枚举

枚举是我们可以确保对所有案件进行详尽说明的另一种方法。 当使用带有Swift枚举的切换条件时,编译器会强制处理所有可能的情况。 即使不注意,程序员也很难错过任何东西。 依靠快速的编译器将释放更多的认知资源来解决更大的问题。

选装件

不要使用隐式展开。 使用了! 产生nil值的运算符会导致运行时错误。 可选参数的目的是使零大小写对开发人员来说很明显,并将其作为预期或意外的情况处理。 主体类似于错误处理。 我们不应忽略可能的错误,也不应忽略可能的值不可用。

面向协议的编程

组合总是优先于继承。 每当发现需要让多个类型共享相同的行为时,请将它们抽象为协议,然后使用扩展名为该协议定义默认行为。 在下面的示例中,通常将Polygon作为基类,并使用Square和Triangle子类Polygon。

子类化确实可以工作,但是测试很尴尬,因为为继承的方法编写测试用例似乎很愚蠢。 但是,有可能方法被意外覆盖。 对于实现协议的纯快速对象,无法覆盖默认扩展名。 因此,在上述情况下仅测试Polygon扩展就足够了。

此外,通过子类化,sides属性将必须从超类继承。 这意味着该属性必须是可变的,以便子类更改它们。 因此,我们将无法排除我们不想考虑的情况。

而且,子类一次只能继承一个类。 如果您需要对许多不同的抽象建模,则很难将它们分开。 这对协议来说不是问题,因为类型可以实现多个不同的协议。

这些也在这里讨论:https://developer.apple.com/videos/play/wwdc2015/408/

类型和协议

AnyObject或AnyClass的用法应为红色标记。 Swift的强类型系统是它的最佳功能之一,我们不应该绕过它。 知道函数期望和返回什么类型,将使人们更容易推断其行为。 实际上,我们可以进一步缩小范围,仅定义所需的协议。

考虑将物品从愿望清单移动到购物车的功能。

该协议从专利上明确了该功能的意图和作用。 在上面的示例中,我们知道购物车上仅执行添加操作,而在心愿单中执行删除操作。 这些对象的状态也保留在磁盘上。

而且,这允许我们传入实现上述协议的任何类型,从而减少耦合。

委托模式

在许多情况下,委托模式不是最佳模式。 一个很好的例子来说明这一点,它是iOS8之前的版本中的可可自己的UIAlertView和新的UIAlertController。

使用旧的UIAlertView,您必须像这样实现委托:

当多个UIAlertViews共享同一委托时,事情变得繁琐。 您必须找出哪个UIAlertView触发了委托。 另外,这还会引入代码碎片,其中显示警报视图的代码和处理解雇的代码位于代码库的不同部分。

使用UIAlertController,我们可以传入闭包来处理后续事件。 每个闭包实例将仅与UIAlert的一个实例相关联,没有歧义。

将职能视为头等公民。 如果发现自己想要实现委托模式,请考虑使用高阶函数(将其他函数作为参数的函数)。

未知国家

许多初学者经常犯的一个错误就是忘记初始化变量。 如果我们忘记设置一个新的声明的原始整数,它包含什么? 这取决于环境和语言而有所不同。 未初始化状态不理想的原因是,如果我们不知道输入,就无法推理行为。

Swift编译器做了很多工作,以确保我们将初始化非可选属性。 我们不应该通过声明如下属性来解决它:

  var something:Int! 

如果在设置之前就已对其进行访问,则应用程序将崩溃。

我喜欢遍历我的代码,确保不行! 使用了运算符。 但这对于IBOutlets是不可能的,因为未在初始化时设置UI元素。 在撰写本文时,唯一避免此问题的方法是在初始化拥有类时以编程方式设置所有UI元素。 我觉得这是一项艰巨的工作,不值得进行,因为在开发时失败是显而易见的。 我希望这会改变。

无状态和副作用

将可变属性引入对象之前,请三思。 每当对象具有要管理的其他状态时,就会在代码中增加复杂性。 下面是一个不好的例子。

问题在于,对状态的任何细微管理都会破坏接触这些状态的所有方法。 我们还必须在处理一个深层链接后清除状态,以确保它对下一个深层链接没有影响。 在上面的示例中,如果我们传入showMsg / thismessage / 3,然后再加上addition / 2 ,则会发生2和3的加法运算

使用此模式的更好模式是适配器模式。 它委派一个Type来处理每个“案例”。 由于每种类型彼此都不了解。 没有副作用或并发症。

无状态还可以帮助您获得功能纯净。 即,函数的所有依赖项都在其参数列表中。 实例方法也常常依赖于实例属性,这是很常见的,我发现这是可以接受的。 但是,对实例外部未通过参数列表传递的内容的依赖是不可取的。 使用单例时,这很常见。 当我们无法避免使用单例时,我们可以依靠单元测试和持续集成来维护强大的代码库。

可测性

单例使测试更加困难,因为一个测试可能会更改单例的状态,因此我们需要为下一个测试重置状态。 如果我们将属性访问设置为只读,则将很难做到。 此外,我们将无法并行运行测试,因为它们都将尝试同时变异相同的单例,

有时,使用单例模式来强制应用程序中的某些行为确实很有意义。 例如,任何时候都只能登录一个用户。

在这种情况下,我们仍然可以通过使用依赖注入来提高测试能力。 对于需要引用单例的任何方法,我们使用带默认值的参数。

考虑一个简单的功能,该功能可以保存当前的国家标志图标。

测试persistentCurrentCountryIcon是有问题的,因为此函数取决于2个单例,我们需要在测试之前进行设置。 利用默认参数,我们得到以下信息:

现在,我们不必设置MyAppConfig单例。 我们可以简单地用一个虚拟密钥调用persistentCurrentCountryIcon,如下所示:

现在,我们需要验证是否已保存。 在Objective-C中,我们将使用方法存根执行此操作,并验证它是否已被调用。 在Swift中,我们可以使用协议。 无需声明dataHandler为MyDataHandler类型,我们可以使用MyDataHandlerSavingProtocol代替。

MyDataHandler当然必须实现该协议。 现在,为了进行测试,我们创建一个存根类,该存根类也实现相同的协议,以便我们可以在测试期间注入它。

瞧! 现在,您可以运行测试而无需碰到单例。

即使您没有运行单个测试,在编码时考虑可测试性也会使代码更健壮。 如果习惯于将所有外部依赖项列为具有默认值的参数,则可能会发现某些方法的默认参数列表很长。 这意味着代码是高度耦合的,是时候重新考虑架构了。

可可ViewControllers

ViewController很难测试,因为ViewController的生命周期是有状态的设计。 例如,要测试viewDidAppear,我们必须确保之前已调用viewDidLoad。

ViewController也主要涉及视图的管理,这从根本上讲是有状态的。 我发现自动的黑匣子测试可以很好地覆盖视图控制器中的情况,前提是视图控制器层很薄。

为了实现薄的viewcontroller层,我们可以将业务逻辑移到viewcontroller引用的单独对象中。 以tableviewController为例,我们可以将该对象设置为tableview委托和数据源。

当您执行此操作时,通常会发生的情况是,您得到的视图控制器仅包含胶水,并且您可以测试模型而无需担心状态。

如果我们绝对必须测试视图控制器,则使用BDD测试方法最简单。 实际上,BDD测试方法相当优雅地处理有状态对象。 我唯一的抱怨是我们只有用于iOS上的BDD测试的第三方框架解决方案。 在撰写本文时,Quick似乎是最有前途的,尽管我不得不说,使用Nimble进行的异步验证经常会崩溃。

即将结束…

始终争取无国籍,不变性和功能纯净。 这些是可测试且健壮的代码的功能。