测试已测试 | ✗ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布上次发布 | 2017年6月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 Wagner Truppel 维护。
TypesafeCountType
。TypesafeIntegerIndexType
。Array
、Set
或 Dictionary
的大小时返回 TypesafeCountType
实例的支持。TypesafeIntegerIndexType
实例进行 Array
下标访问的支持。UniqueIntegerType
的类型现在都可以使用 Int
初始化自身并检索基础值作为 Int
。当操作无法执行时,这两个操作的结果都是 nil。请期待将来为 UniqueIntegerType
和 UniqueFloatingPointType
添加的算术运算符。这些很多,所以需要一些时间,但支持这些运算符的功能 正在 添加。
UniqueBooleanType
现在支持所有标准布尔运算符(!
、&&
和 ||
)。
我敢肯定,你一定遇到过代码中的相同原始类型的属性,但它们的语义意义大不相同的情况。例如,假设你有一些值类型(诚然,这是一个奇怪的例子)
struct User {
let id: Int
let name: String
var isConnected: Bool
var coreTemperature: Double
var numberOfPets: Int
init(id: Int, name: String, coreTemperature: Double, numberOfPets: Int = 0) {
self.id = id
self.name = name
self.isConnected = false
self.coreTemperature = coreTemperature
self.numberOfPets = numberOfPets
}
}
struct NuclearReactor {
let id: Int
let name: String
var isConnected: Bool
var coreTemperature: Double
var numberOfFalseAlarms: Int
init(id: Int, name: String, coreTemperature: Double) {
self.id = id
self.name = name
self.isConnected = false
self.coreTemperature = coreTemperature
self.numberOfFalseAlarms = 0
}
}
显然,用户拥有的宠物数量和他的用户ID没有关系。然而,编译器会高兴地让您将用户ID的值设置为他的宠物数量,就像这样
let numberOfPets = 3
let user = User(id: numberOfPets, name: "John Doe", coreTemperature: 36.7)
也许你声称绝不会混淆用户ID与他的宠物数量。好吧,那么用用户ID而不是反应堆ID初始化核反应堆,怎么样,就像这样
let userId = 10
// ... sometime later ...
let id = userId
// ... sometime later, after you've forgotten that `id` is set to `userId` ...
let reactorId = id
let reactor = NuclearReactor(id: reactorId, name: "Homer's Favorite", coreTemperature: 10_000.0)
或者用反应堆的内核温度初始化用户,就像这样
let coreTemp = reactor.coreTemperature
// ... sometime later ...
let user2 = User(id: userId, name: "Jim Doe", coreTemperature: coreTemp)
这些错误太过容易犯,发生得太频繁。是不是很好,当您试图将一种类型的值设置为另一种类型的值时,编译器可以警告您,这两种类型都使用相同的基础原始类型进行存储?
嗯,这正是这个库能让你做的事情。
附加说明:你可能喜欢阅读我的同事所写的这篇博客文章 避免Swift中的原始数据类型偏好。它详细介绍了我朋友所说的应该避免的原始数据类型偏好的原因。这是一篇很好的阅读。
这里有四个协议:
UniqueBooleanType
:用于自定义布尔类型。UniqueIntegerType
:用于基于任何整数类型的自定义类型。UniqueFloatingPointType
:用于基于任何浮点类型自定义类型。UniqueStringType
:用于自定义字符串类型。以及你需要知道的两个结构体
TypesafeIntegerIndexType
:用于表示给定元素类型的数组中的索引。TypesafeCountType
:用于表示任何给定类型的数量或计数。要使用这些协议,首先在你项目的某个地方声明以下类型的类型。你需要它们,但你不会经常直接使用它们:
struct Id<T>: UniqueIntegerType {
public typealias PrimitiveType = Int
public let value: Int
public init(_ value: Int) {
self.value = value
}
}
struct Name<T>: UniqueStringType {
public typealias PrimitiveType = String
public let value: String
public init(_ value: String) {
self.value = value
}
}
struct Connected<T>: UniqueBooleanType {
public typealias PrimitiveType = Bool
public let value: Bool
public init(_ value: Bool) {
self.value = value
}
}
struct CoreTemperature<T>: UniqueFloatingPointType {
public typealias PrimitiveType = Double
public let value: Double
public init(_ value: Double) {
self.value = value
}
}
你也可以创建非泛型类型和不是结构体的类型。例如,如果你的应用程序中只支持密码的类型是User
类型,而你想要将密码声明为类而不是结构体,那么你可以声明如下:
class Password: UniqueStringType {
public typealias PrimitiveType = String
public let value: String
public init(_ value: String) {
self.value = value
}
}
无论如何,下一步是创建实际的数据类型,如下所示:
typealias UserId = Id<User>
typealias UserName = Name<User>
typealias UserConnected = Connected<User>
typealias UserCoreTemperature = CoreTemperature<User>
typealias CountOfPets = TypesafeIntCount<Pets> // assuming you have declared a `Pet` type
struct User {
let id: UserId
let name: UserName
let password: Password
var isConnected: UserConnected
var coreTemperature: UserCoreTemperature
var numberOfPets: CountOfPets
}
typealias ReactorId = Id<NuclearReactor>
typealias ReactorName = Name<NuclearReactor>
typealias ReactorConnected = Connected<NuclearReactor>
typealias ReactorCoreTemperature = CoreTemperature<NuclearReactor>
typealias CountOfFalseAlarms = TypesafeIntCount<FalseAlarm> // assuming you have declared a `FalseAlarm` type
struct NuclearReactor {
let id: ReactorId
let name: ReactorName
var isConnected: ReactorConnected
var coreTemperature: ReactorCoreTemperature
var numberOfFalseAlarms: CountOfFalseAlarms
}
注意这些类型读起来有多清晰。比如,Name<User>
和Id<User>
读起来非常明确,更是自解释的,同时又是类型安全的。
此外,请注意,你不必为计数声明结构体。相反,你只需要将泛型唯一整数计数类型的特定具体版本异名到TypesafeIntCount
。如果你不希望计数由Int
实例支持,而是由比如UInt
实例支持,那么你可以使用TypesafeUIntCount
。更进一步,有一个泛型struct TypesafeCountType<CountType: Integer, TargetType>
,你可以用来声明任何类型Integer
作为后盾的唯一计数类型。
现在,每当你尝试使用另一个属性中的值设置一个属性值,或者将错误类型的值传递给一个函数时,编译器都会警告你的类型不匹配。例如,以下代码块将不会编译
let reactor = NuclearReactor(id: 100,
name: "Homer's Favorite",
isConnected: true,
coreTemperature: 10_000.0)
let coreTemp = reactor.coreTemperature
let user2 = User(id: 5,
name: "Jim Doe",
isConnected: false,
coreTemperature: coreTemp)
因为你正在尝试将用户的核温度设置为反应堆的核温度。以下是在这种情况我从Xcode中得到的警告截图
但是等着!你注意到我没有使用长形式来调用新创建的类型初始化器,就像下面的代码吗?
let reactor = NuclearReactor(id: ReactorId(100),
name: ReactorName("Homer's Favorite"),
isConnected: ReactorConnected(true),
coreTemperature: ReactorCoreTemperature(10_000.0))
这是因为这些类型在适当的情况下遵守ExpressibleByIntegerLiteral
之类的协议,这允许你使用文字值而不是显式的初始化调用。不幸的是,这只适用于文字。有时你可能得写些像下面这样的东西
let userName = UserName("John Doe [\(userId.description)]")
因为编译器不会接受
let userName = "John Doe [\(userId.description)]"
因为右侧已不再是文字字符串值。
等等,还有更多!
如果你有一个用户ID数组,比如,怎么处理隐藏在内的原始值?简单。你使用boxed()
和unboxed()
函数。例如,
let array1 = [1, 2, 3]
let userIds: [UserId] = array1.boxed()
将从一个原始类型数组(在这种情况下,是Int
)创建一个UserId
实例数组。类似的,
let array2: [Int] = userIds.unboxed()
将返回一个包含所有原始值的数组。请注意,您需要通过声明期望返回的变量类型来帮助编译器推断正确的类型,正如上面为 userIds
和 array2
所做的那样。
这种在封装和解封装自定义类型集合之间来回切换的便利性适用于 Array
、Set
和 Dictionary
的实例。例如,您可以有一个将用户 ID 映射到用户名的字典,如下所示
var userIdToUserNameMap: [UserId, UserName] = [:]
等等……难道你不担心字典键类型必须符合 Hashable
吗?不,您不必担心,因为原始类型本身就是可哈希的,并且支持这个库工作的底层协议利用了这个事实为您实现了 Hashable
。因此,所有您的自定义 WTUniquePrimitiveType
创建都已符合 Hashable
。它们还符合 Equatable
和 Comparable
丛东!
version 1.0.2
新增了在查询这些类型之一大小时返回唯一计数类型的功能。例如,您有一个用户 ID 的 Array
和一个像这样的核反应的 Set
,
let array: [UserId] = ...
let reactors: Set<NuclearReactor> = ...
如果您访问这些的 count
只读属性,您将得到标准的 Int
值,但如果您想获取类型安全的计数,则可以访问 typesafeCount
,因为它返回的计数专门针对存储在当前集合中的元素的类型。所以,
let userIdCount = array.typesafeCount
let reactorCount = reactors.typesafeCount
将具有不同的类型
userIdCount
的类型为 TypesafeIntIndexType
,reactorCount
的类型为 TypesafeIntIndexType
。
更普遍地说,数组、集合或字典的 typesafeCount
属性的类型是 TypesafeIntIndexType
,其中 ET
是存储在该集合中的实例的类型。
version 1.0.2
的新增特性是使用类型安全的索引来索引和下标数组。所以现在,您可以通过声明
typealias IndexOfUserId = TypesafeIntIndexType<UserId>
来有一个类似于 IndexOfUserId
的类型,
var userIds: [UserId] = ...
let userId = userIds[IndexOfUserId(2)] // index 2 returns the 3rd element, as usual
userIds[IndexOfUserId(4)] = UserId(...) // sets a new userID for the 5th array entry
具有类型安全的索引可以防止将一种数组的索引传递给另一种类型的数组,这是常见的错误。
Integer
的唯一原始类型的特殊支持有时您想要通过使用非 Int
的 Integer
属性来节约内存,而是像 Int16
或 Int8
这样的更小的东西。有时您想确保所讨论的整数是无符号的,因此您想要像 UInt
这样的东西。有时您可能希望一个属性同时具备这两种特性,比如 UInt32
。
在所有这些情况下,您通常仍然希望通过 Int
值来初始化它们(在可能的情况下),并将它们作为普通的 Int
实例来引用(再次,只在可能的情况下)。
现在,在version 1.0.2
版本中,您可以为任何使用Integer
类型的 uniquely backed 原始数据类型使用Int
值进行(尝试)初始化,并且可以使用计算属性valueAsInt
(仍然和总是通过计算属性value
返回底层值,相应地类型化)来访问底层值。请注意,当底层 Integer
类型的转换到和从 Int
不可能时,初始化程序和valueAsInt
都会返回nil
。
例如,您不能使用大于127的Int8
类型的唯一原始数据类型或任何由UnsignedInteger
类型支持的唯一原始数据类型的负Int
值进行初始化。同样,由UInt64
支持的唯一原始数据类型的值范围比Int
更广,因此您不一定总能得到底层值的Int
表示。
这是一件我还未做但是计划在库的version 1.1
中添加的事情,所以请继续关注。目前,您必须通过使用getter属性value
来访问内部值,如下所示:
let total = user.numberOfPets.value + 3
很快您将能够编写
let total = user.numberOfPets + 3
而不是,此时如果您编写
let total = user.numberOfPets + reactor.numberOfFalseAlarms
好的,现在您也会得到警告,但原因不同,即操作符+
不能应用于给定类型的操作数。
库达到了100%的测试覆盖率。
存在一个基本的协议
public protocol WTUniquePrimitiveType: CustomStringConvertible, Equatable, Comparable, Hashable {
associatedtype PrimitiveType: CustomStringConvertible, Equatable, Comparable, Hashable
var value: PrimitiveType { get }
init(_ value: PrimitiveType)
}
要求任何符合该协议的类型声明适当的存储以及为此存储的初始化器。
然后,有针对单个原始数据类型的高级协议。例如,以下是基于Integer
的任何原始数据类型的协议示例:
public protocol UniqueIntegerType: WTUniquePrimitiveType, ExpressibleByIntegerLiteral {
associatedtype PrimitiveType: Integer
}
其余的都是使用协议扩展实现这些类型应具有的共同行为的简单问题。
这个库是用Swift 3.1和Xcode 8.3.2构建和设计的。
WTUniquePrimitiveType通过CocoaPods提供。要安装它,只需在Podfile中添加以下行:
pod "WTUniquePrimitiveType"
如果这不起作用(偶尔会不起作用),请尝试以下方法:
pod 'WTUniquePrimitiveType', :git => 'https://github.com/wltrup/Swift-WTUniquePrimitiveType.git'
wltrup, [email protected]
WTUniquePrimitiveType在MIT许可下可用。有关更多信息,请参阅 LICENSE 文件。