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

Posted

技术标签:

【中文标题】在 Swift 中安全地从 Int 向下转换为 Int8【英文标题】:Safely down casting from Int to Int8 in Swift 【发布时间】:2017-06-14 18:15:24 【问题描述】:

我通过 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

【问题讨论】:

您能否展示一下您是如何创建JSONDictionary 的?如果 NSNumbers 的值不是源自 Swift(非 Int8)到 Obj-C 桥,则演员 as? Int8 应该可以正常工作 - 尽管 NSNumbers 的值超出了 @ 的范围987654335@,多余的位将被截断。 已更新以回答您的问题。 嗯,奇怪的是 as? Int 在你的情况下不起作用(尽管在任何情况下,你已经声明你不想截断行为,所以你不想使用无论如何)-但this example demonstrates演员应该工作。 实际上,您的示例也失败了(没有Any 演员表):example NSNumberInt8 的转换是SE-0139 的一部分,并在Swift 3.0.1 中实现。 【参考方案1】:

以下是将Int(来自NSNumber)转换为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。因此,它可以像您一样使用 optional binding 进行检查,或者如果您有适当的值,它可以与 nil 合并运算符 ?? 结合使用以提供默认值:

// Determine Int8 value, use 0 if value would overflow an Int8
let j = Int8(exactly: i) ?? 0

在Swift 3.0.1及以上的as Int8直接投射NSNumber

正如 cmets 中提到的 @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) 更好/更安全,因为我不想要截断行为。 我更新了我的答案。它结合了来自 cmets 的信息。 非常感谢 - 对于可能想知道为什么基本(直观)Int8 转换失败的其他人来说,这是一个极好的资源。就个人而言,我升级到了 Swift 3.0.1。 truncatingBitPattern 现在重命名为 truncatingIfNeeded。【参考方案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

【讨论】:

以上是关于在 Swift 中安全地从 Int 向下转换为 Int8的主要内容,如果未能解决你的问题,请参考以下文章

在java中,两个子类可否互相转换?

这应该如何正确地从 Objective C 转换为 swift 3

检查字典中的对象是不是为 Int (Swift)

基本数据类型转换之向上转型和向下转换

Swift数组不能向下转换为派生数组[重复]

从 AnyObject 向下转换为 UIImage[] - Swift