Swift中将Int安全地向下转换为Int8

8
我通过API的JSON响应接收到一组整数(我将其转换为[String: Any]字典)。这些整数保证在-10...-10范围内(包括边界)。在我的模型中,我想将它们存储为Int8。
以下是我将JSON字典转换为模型对象的方法,但我想知道是否有更惯用的方法来确保JSON中的整数实际上适合Int8?
func move(with dict: JSONDictionary) -> Move? {
    if let rowOffset = dict[JSONKeys.rowOffsetKey] as? Int,
       let colOffset = dict[JSONKeys.colOffsetKey] as? Int {

      if let rowInt8 = Int8(exactly: rowOffset),
         let colInt8 = Int8(exactly: colOffset) {

        return Move(rowOffset: rowInt8, colOffset: colInt8)
      }
      else {
        print("Error: values out of range: (row: \(rowOffset), col: \(colOffset)")
      }
    } else {
      print("Error: Missing key, either: \(JSONKeys.rowOffsetKey) or \(JSONKeys.colOffsetKey)")
    }

    return nil
  }

请注意,无论传入的整数的值如何,始终会失败执行以下操作:
if let rowOffset = dict[JSONKeys.rowOffsetKey] as? Int8,
   let colOffset = dict[JSONKeys.colOffsetKey] as? Int8 {
...

这是我将传入的JSON转换为字典的方法。所涉及的JSON嵌套很深,并包含多种不同类型。
typealias JSONDictionary = [String: Any]
let jsonDict = try JSONSerialization.jsonObject(with: data) as? JSONDictionary

1
请问您能展示一下如何创建JSONDictionary吗?只要这些值是来自于Swift(非Int8)到Obj-C桥接的NSNumber,那么as? Int8转换应该可以正常工作 - 尽管对于超出Int8范围的NSNumber,额外的位将被截断。 - Hamish
已更新以回答您的问题。 - RobertJoseph
1
哦,奇怪啊,在你的情况下 as? Int 不起作用(尽管无论如何,你已经说过你不想要截断行为,所以你也不会想使用它),但是这个示例演示出转换应该可以工作。 - Hamish
实际上,您的示例也失败了(没有 Any 强制转换):示例 - RobertJoseph
3
NSNumber转换为Int8SE-0139的一部分,已在Swift 3.0.1中实现。 - OOPer
显示剩余4条评论
2个回答

14
以下是将来自于 NSNumberInt 转换为 Int8 的一些选项: 使用 Int8 的初始化器 init(_: Int) 进行转换 如果您的 Int 值可以保证适合 Int8,那么使用 Int8(value) 进行转换是可以的。
如果您得到一个不适合 Int8Int,则程序将崩溃:
let i = 300
let j = Int8(i)  // crash!

使用 Int8 的初始化方法 init(truncatingIfNeeded: BinaryInteger) 进行初始化

为了更加安全,建议使用 init(truncatingIfNeeded: BinaryInteger) 初始化方法:

let i = 300
let j = Int8(truncatingIfNeeded: i)  // j = 44

这确实会生成一些不太理想的修改值,但可以防止崩溃。
明确检查有效值的范围
作为另一种选择,您可以明确地检查范围:
if (-10...10).contains(i) {
    j = Int8(i)
} else {
    // do something with the error case
}

检查的优点在于您可以指定有效范围,而不仅仅是在超出适合 Int8 值的范围时检测错误。
使用 Int8 的初始化器 init(exactly: Int) 进行初始化
您的代码目前正在使用此方法安全地初始化 Int8 值。这是强大的,因为如果该值不适合 Int8,它将返回 nil。因此,它可以与您正在进行的可选绑定一起检查,或者可以与 nil 合并运算符 ?? 结合使用,以提供默认值(如果您有适当的值)。
// Determine Int8 value, use 0 if value would overflow an Int8
let j = Int8(exactly: i) ?? 0

Swift 3.0.1及以上版本中,可以直接使用as Int8NSNumber转换为Int8

正如评论中@Hamish和@OOPer提到的那样,现在可以直接将NSNumber转换为Int8

let i: NSNumber = 300
let j = i as Int8  // j = 44

这与使用 init(truncatingIfNeeded: BinaryInteger) 一样具有截断效果。
总之,你当前的代码是安全的,而且可能是解决问题的最佳方案,除非你想检测值是否超出了 -10...10 的当前期望范围,在这种情况下,显式检查将是更好的选择。

目前我使用的API只返回-10...-10范围内的值,但这可能会在未来发生变化。 (Int8.min...Int8.max).contains(i) 是否更好?此外,我不确定为什么 init(truncatingBitPattern: Int)Int8(exactly: Int) 更好/更安全,因为我不想要截断行为。 - RobertJoseph
2
我更新了我的答案。它包含了评论中的信息。 - vacawama
1
非常感谢 - 这对于其他可能想知道为什么基本(直观)的 Int8 转换失败的人来说是一个很棒的资源。就我个人而言,我已经升级到 Swift 3.0.1。 - RobertJoseph
truncatingBitPattern 现在已更名为 truncatingIfNeeded。 - Reza
另一个(不太为人知的?)选项是numericCast。它是一个通用函数,如果您需要将Swift整数传递给C函数并且“知道”它适合目标类型,则非常方便。 - Martin R

2

如@vacawama所说

let j = Int8(exactly: i) ?? 0 

将Int转换为UInt8是一种完美且安全的方法,但有时我需要获取UInt8的最大和最小值而不是零。我使用的是扩展程序:

extension UInt8 {

    /// Safely converts Int to UInt8, truncate remains that do not fit in UInt8.
    /// For instance, if Int value is 300, UInt8 will be 255, or if Int value is -100, UInt8 value will be 0
    init(truncateToFit int: Int) {
        switch int {
        case _ where int < UInt8.min: self.init(UInt8.min)
        case _ where int > UInt8.max: self.init(UInt8.max)
        default: self.init(int)
        }
    }

    /// Safely converts Float to UInt8, truncate remains that do not fit in UInt8.
    /// For instance, if Float value is 300.934, UInt8 will be 255, or if Float value is -100.2342, UInt8 value will be 0
    init(truncateToFit float: Float) {
        switch float {
        case _ where float < Float(UInt8.min): self.init(UInt8.min)
        case _ where float > Float(UInt8.max): self.init(UInt8.max)
        default: self.init(float)
        }
    }

    /// Safely converts Double to UInt8, truncate remains that do not fit in UInt8.
    /// For instance, if Double value is 300.934, UInt8 will be 255, or if Double value is -100.2342, UInt8 value will be 0
    init(truncateToFit double: Double) {
        switch double {
        case _ where double < Double(UInt8.min): self.init(UInt8.min)
        case _ where double > Double(UInt8.max): self.init(UInt8.max)
        default: self.init(double)
        }
    }
}

例如:
    let value = UInt8(truncateToFit: Int.max) // value == 255

更新:

我找到了符合BinaryInteger协议的所有数字的标准实现,例如Int、Int8、Int16、Int32等。

let value = UInt8(clamping: 500) // value == 255
let secondValue = UInt8(clamping: -500) // secondValue == 0

但是对于Double和Float,有一种优雅的解决方案,即为所有BinaryInteger类型提供一个扩展。
extension BinaryInteger where Self: FixedWidthInteger {

    /// Safely converts Float to BinaryInteger (Uint8, Uint16, Int8, and so on), truncate remains that do not fit in the instance of BinaryInteger range value.
    /// For instance, if Float value is 300.934, and self is UInt8, it will be 255, or if Float value is -100.2342, self value will be 0
    init(truncateToFit float: Float) {
        switch float {
        case _ where float < Float(Self.min): self.init(Self.min)
        case _ where float > Float(Self.max): self.init(Self.max)
        default: self.init(float)
        }
    }

    /// Safely converts Double to BinaryInteger (Uint8, Uint16, Int8, and so on), truncate remains that do not fit in the instance of BinaryInteger range value.
    /// For instance, if Double value is 300.934, and self is UInt8, it will be 255, or if Float value is -100.2342, self value will be 0
    init(truncateToFit double: Double) {
        switch double {
        case _ where double < Double(Self.min): self.init(Self.min)
        case _ where double > Double(Self.max): self.init(Self.max)
        default: self.init(double)
        }
    }
}

例如:
let valueUInt16 = UInt16(truncateToFit: 5000000.0) // valueUInt16 == 65535
let valueInt8 = Int8(truncateToFit: 5000000.0)  // valueInt8 == 127
let valueUInt8 = UInt8(truncateToFit: -500.0)   // valueUInt8 == 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接