问题
每个应用程序都有 数据模型。数据模型通常以自定义复合 数据类型 的形式实现,该类型存储一个或多个 属性。理想状态下,每个属性的类型(无论是原子的还是复合的)定义了这个属性的所有可能值的集合。此外,可能还有一些特殊的规则来定义任何给定值是否适合该属性。
Swift 没有内置机制,无法在标准数据类型中缩小允许值的集合,也无法对每个给定的值进行评估以检查其是否适合该属性。
例如,要限制一个值仅为 1 到 100 范围内的整数,并避免所有奇数,我们通常会使用整数,然后以某种方式在将该值放入属性之前执行必要的检查。这导致至少有两个(有时更多)代码库中的单一业务逻辑片段(对该特定属性的特定属性要求)分布。
愿望清单
- 简洁的值验证逻辑内联定义;
- 在将值实际写入属性之前进行安全的值验证;
- 组合多个要求以创建定义自定义允许值集的复合要求;
- 通过将验证逻辑编写为纯函数消除任何副作用。
如何安装
推荐使用CocoaPods进行安装。
工作原理
数据类型 ValidatableValue
表示可以验证的值,可以针对自定义规则/要求进行验证。它还依赖于表示潜在值的基本数据类型的泛型(标准系统数据类型之一或任何自定义类型)Value
。必须以输入参数的形式提供验证逻辑,即 Requirement 实例集合(在基类型值都合适的边界情况下可能为空)。创建可验证值的几种方法见下文。
如何使用
假设我们需要定义一个基本的数据模型来表示我们的应用程序中的用户。
按照以下方式导入必要的库
import XCEValidatableValue
import XCERequirement
让我们声明一个名为 MyUser
的类型
struct MyUser
{
// ...
}
在这个类型内部,我们定义实例级别的变量,类型为 ValidatableValue
,为一个属性定义一个。
我们可以创建一个不允许任何输入的常量值(一个边界情况),所以我们将其声明为 let
let someConstantValue = ValidatableValue(3)
以下是带有单个验证要求的可验证值的简单示例
var firstName = ValidatableValue<String>(
Require("Non-empty") { $0.characters.count > 0 } )
注意,我们将其声明为 var
,因为默认情况下该值为空,需要设置一个通过验证的值才能使整个值有效。
创建不带有验证规则的可验证值也是可以的。这是一个边界情况,它没有任何值,但这允许统一源代码和对数据模型属性的操作方法。下面是示例。
var lastName = ValidatableValue<String?>()
// no requirements on value, even "nil" is okay
下面是一个包含多个要求的复杂验证逻辑示例。
var password = ValidatableValue<String>(
Require("Lenght between 8 and 30 characters"){ 8...30 ~= $0.characters.count },
Require("At least 1 capital character"){ 1 <= Pwd.caps.count(at: $0) },
Require("At least 4 lower characters"){ 4 <= Pwd.lows.count(at: $0) },
Require("At least 1 digit character"){ 1 <= Pwd.digits.count(at: $0) },
Require("At least 1 special character"){ 1 <= Pwd.specials.count(at: $0) },
Require("Valid characters only"){ Pwd.allowed.isSuperset(of: CS(charactersIn: $0)) })
在上面的例子中,我们使用了如下定义的简单自定义助手
enum Pwd
{
static
let caps = CS.uppercaseLetters
static
let lows = CS.lowercaseLetters
static
let digits = CS.decimalDigits
static
let specials = CS(charactersIn: " ,.!?@#$%^&*()-_+=")
static
var allowed = caps.union(lows).union(digits).union(specials)
}
稍后,我们可以在不必担心立即提供所需值的情况下创建数据模型实例(这在需要存储和验证将在保存到服务器后持久保存的数据之前的情况中非常有用)。请注意,开发者有责任定义哪些属性的组合意味着整个数据模型是有效的,可以保存到长期/外部内存中。
以下是创建用户模型实例的方法
let u = MyUser()
在任何时间点,我们都可以检查一个值是否在当下有效
u.someConstantValue.isValid() // constants will always return 'true'
以下是访问实际值的方式,如果该值未通过验证,它将抛出 InvalidValue
错误
try u.someConstantValue.value()
要设置值,使用 setValue
方法,它接受任何类型的数据,如果传入的值未通过验证,将抛出错误;如果内部存储值已更新并通过静默(在这种情况下,整个可验证值变为有效)
let newVal: Any = //...
try u.firstName.setValue(newVal)
请参阅单元测试中的完整示例:单元测试。