【问题标题】:Can you enforce a typealias in swift你能快速强制执行类型别名吗
【发布时间】:2018-07-23 02:13:36
【问题描述】:

我正在尝试强制执行由 Int 支持的简单类型不会与其他 Ints 混淆。

假设你有以下类型别名:

typealias EnemyId = Int
typealias WeaponId = Int

我希望以下出现编译器错误:

var enemy: EnemyId = EnemyId("1")
enemy = WeaponId("1") // this should fail

我想失败的那行,应该失败,因为这两种类型(EnemyId 和 WeaponId)是不同的类型。

实现这一目标最好、最干净的方法是什么?

更新

在查看了答案和 cmets 之后,我想添加我使用枚举想出的内容:

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
    }
  }

然而,我的解析结果增加了几百毫秒,因此我不得不恢复到 typealias,但它非常接近我想要的。

【问题讨论】:

  • 这看起来像enum 会更有用。这样,编译器可以防止您发生此类意外转换,您永远不必担心无效的敌人或武器类型,并且您可以使用 Weapon.sword(在大多数情况下甚至是 .sword)来引用值,而不必声明像 WEAPON_SWORD 这样的常量。
  • @NobodyNada 我也玩过枚举,但是预定义每个可能的 ID 是多余的。至少我无法找到令人满意的解决方案。

标签: swift types


【解决方案1】:

在引入 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) 

【讨论】:

    【解决方案2】:

    Swift 还没有(还没有?)newtype 的概念——基本上是一种不透明类型,其值与原始类型相同。

    您可以做的是使用包装原始类型的单字段结构。 1-field 结构没有性能损失,同时为您提供了一个独特的类型来使用,具有更多的语义价值(感谢@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 这样的类型可以在很多地方使用,但又有所不同。当然,您可以根据需要添加更多的协议一致性。

    【讨论】:

    • 在大多数情况下,与其取消引用值,不如直接在结构上提供功能。在许多情况下,value 应被视为内部实现细节。 (如果你需要很多方法,它确实会变得有点乏味,但你会得到更多的价值。希望我们最终能找到一个解决方案,让你可以通过特定的协议。(有一些令人头疼的问题与相关的类型,但我们可以希望。)
    • 不要减损这个答案(这是完全正确的 IMO),但我添加了一些代码来展示如果你有一堆这些(就像我经常做的那样)如何更普遍地做到这一点。
    • 实际上在 Swift 4.1 中,这方式变得更容易了。你只需要遵循 Hashable,hashValue== 就可以完全恢复活力!你只需要init,如果你不介意在初始化器中说value:,你也可以省略它。
    • @Cristik 我喜欢newtype 的概念,也许应该将它添加到swift。这正是我正在寻找的。 :)
    【解决方案3】:

    请注意,在 Swift 4.1 中,像 Rob 和 Cristik 这样的包装器结构建议编写起来变得微不足道,因为当您声明采用 Hashable 时,您会得到hashValue==自动合成实现场景。

    因此,出于您的目的,编写以下内容可能就足够了:

    struct EnemyId: Hashable {
        let value: Int
    }
    struct WeaponId: Hashable {
        let value: Int
    }
    

    在这两种情况下,您都会“免费”获得成员初始化器 init(value:) 加上 == 加上 hashValue

    【讨论】:

    • 提到成员初始化器是internal,因此如果结构体在另一个模块中定义,则不可用。
    • @Cristik 谢谢,我不知道(虽然我记得在 4.1 注释中有关于初始化程序和其他模块的内容)。
    【解决方案4】:

    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。但是这种协议/扩展确实使单字段结构(“类型提升”)更加可口。正如您所描述的那样,我在所有地方都将这些用于 ID。

    附带说明:这些在 Swift 中通常非常便宜。结构没有存储开销,因此包含 Int 的结构的内存使用量只是 Int。通过全模块优化,Swift 通常也可以内联大部分间接。

    【讨论】:

      【解决方案5】:

      你可以尝试一个小包装结构:

      struct EnemyID {
          let value: Int
      
          init?(_ value: String) {
              guard let intValue = Int(value) else { return nil }
              self.value = intValue
          }
      }
      

      ...WeaponID 也是如此。

      【讨论】:

        猜你喜欢
        • 2017-09-20
        • 2014-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-19
        • 1970-01-01
        • 2021-06-10
        • 2013-05-15
        相关资源
        最近更新 更多