你能在swift中强制执行一个typealias吗?

时间:2018-02-12 19:50:44

标签: swift types

我正在尝试强制执行由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
    }
  }

但是我的解析却增加了几百毫秒,因此我不得不恢复到类型,但它非常接近我的想法。

5 个答案:

答案 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)