我正在尝试强制执行由Int
支持的简单类型,但不会与其他Int
混淆。
假设您有以下类型:
typealias EnemyId = Int
typealias WeaponId = Int
我希望以下内容有编译错误:
var enemy: EnemyId = EnemyId("1")
enemy = WeaponId("1") // this should fail
我想要失败的那一行应该失败,因为两种类型(EnemyId和WeaponId)是不同的类型。
实现这一目标的最好,最干净的方法是什么?
更新
在审核了答案和评论之后,我想用枚举添加我想出的内容:
enum Enemy {
case id(Int)
var id: Int {
switch self {
case .id(let i):
return i
}
}
}
let enemy = Enemy.id(1)
print("enemy: \(enemy.id)")
目前,Mattt的答案要短得多,而且更符合您对Swift的期望。
更新#2
我还没有访问swift 4.1所以我必须执行以下操作:
struct EnemyId : Hashable {
private let value: Int
init(_ value: Int) {
self.value = value
}
init?(_ string:String) {
guard let value = Int(string) else { return nil }
self.init(value)
}
static func ==(_ lhs: EnemyId, _ rhs: EnemyId) -> Bool {
return lhs.value == rhs.value
}
var hashValue: Int {
return value.hashValue
}
}
但是我的解析却增加了几百毫秒,因此我不得不恢复到类型,但它非常接近我的想法。
答案 0 :(得分:4)
Swift没有(但是?)有newtype
的概念 - 基本上是一个不透明的类型,后面存储了与原始类型相同的值。
你能做的是使用包裹原始类型的1字段结构。 1字段结构没有性能损失,同时为您提供了一个独特的类型,具有更多的语义价值(感谢@RobNapier提供关于Hashable
的精彩提示):
struct EnemyId: Hashable {
private let value: Int
init(_ value: Int) { self.value = value }
static func ==(_ lhs: EnemyId, _ rhs: EnemyId) -> Bool {
return lhs.value == rhs.value
}
var hashValue: Int {
return value.hashValue
}
}
struct WeaponId: Hashable {
private let value: Int
init(_ value: Int) { self.value = value }
static func ==(_ lhs: WeaponId, _ rhs: WeaponId) -> Bool {
return lhs.value == rhs.value
}
var hashValue: Int {
return value.hashValue
}
}
这样的类型可以在许多地方使用,Int
可以使用,但不同。当然,您可以根据需要添加更多协议一致性。
答案 1 :(得分:4)
请注意,在Swift 4.1中,像Rob和Cristik这样的包装器结构建议写入变得微不足道,因为当您声明采用Hashable时,您会得到{em>自动合成的实现 hashValue
和{{ 1}}在幕后。
因此,出于您的目的,写下来可能就足够了:
==
在这两种情况下,您都可以免费获得成员初始值设定项struct EnemyId: Hashable {
let value: Int
}
struct WeaponId: Hashable {
let value: Int
}
以及init(value:)
加==
“。
答案 2 :(得分:2)
Cristik完全正确的IMO。如果您只有其中的一部分,您可以手动编写协议代码,但以完全相同的方式实现Equatable
半次可能会有点繁琐。如果你遇到这种情况,你可以使用像这样的协议使这种事情更加自动化:
protocol NumberConvertible: CustomStringConvertible, Comparable {
init(number: NSNumber)
var numberValue: NSNumber { get }
}
// CustomStringConvertible
extension NumberConvertible {
var description: String { return numberValue.description }
}
// Comparable
func == <N: NumberConvertible>(lhs: N, rhs: N) -> Bool {
return lhs.numberValue == rhs.numberValue
}
func < <N: NumberConvertible>(lhs: N, rhs: N) -> Bool {
return lhs.numberValue.int64Value < rhs.numberValue.int64Value
}
这只是我碰巧使用NSNumber
更容易与Core Data互操作的一部分。显然,你可以构建一个类似的IntConvertible
,它可以在不需要NSNumber
的情况下以相同的方式工作。但是这种协议/扩展确实使得单字段结构(&#34;类型提升&#34;)更加可口。我将这些ID用于所有地方的ID,正如您所描述的那样。
作为旁注:这些在Swift中通常非常便宜。结构没有存储开销,因此包含Int的结构的内存使用只是Int。通过整体模块优化,Swift通常也可以内联大部分内容。
答案 3 :(得分:1)
您可以尝试一些包装结构:
struct EnemyID {
let value: Int
init?(_ value: String) {
guard let intValue = Int(value) else { return nil }
self.value = intValue
}
}
...和WeaponID
相同。
答案 4 :(得分:0)
在引入 newtype
之前,您可以使用以下内容:
struct IntID<T> {
var value: Int
}
...
typealias EnemyID = IntID<Enemy>
typealias WeaponID = IntID<Weapon>
...
let enemyID = EnemyID(value: 1)
let weaponID = WeaponID(value: 1)
在这种情况下,在此示例中调用 blah
之类的函数时,它不会允许您犯订单错误:
func blah(enemyID: EnemyID, weaponID: WeaponID) {...}
...
blah(enemyID: enemyID, weaponID: weaponID) //works
blah(enemyID: weaponID, weaponID: enemyID) //doesn't
除此之外,您还可以添加对 ExpressibleByIntegerLiteral
extension IntID: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self.value = value
}
}
这将允许您执行以下操作:
let enemyId: EnemyID = 2
let weaponID: WeaponID = 4
blah(enemyID: enemyID, weaponID: weaponID)
blah(enemyID: 6, weaponID: 7)
// but this one will not compile
let intValue = 42
blah(enemyID: intValue, weaponID: intValue)