在 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
的?如果 NSNumber
s 的值不是源自 Swift(非 Int8
)到 Obj-C 桥,则演员 as? Int8
应该可以正常工作 - 尽管 NSNumber
s 的值超出了 @ 的范围987654335@,多余的位将被截断。
已更新以回答您的问题。
嗯,奇怪的是 as? Int
在你的情况下不起作用(尽管在任何情况下,你已经声明你不想截断行为,所以你不想使用无论如何)-但this example demonstrates演员应该工作。
实际上,您的示例也失败了(没有Any
演员表):example
从NSNumber
到Int8
的转换是SE-0139 的一部分,并在Swift 3.0.1 中实现。
【参考方案1】:
以下是将Int
(来自NSNumber
)转换为Int8
的一些选项:
只是用Int8
的初始化器init(_: Int)
进行转换
如果您的Int
值保证适合Int8
,那么用Int8(value)
转换它们就可以了。
如果你得到一个不适合Int8
的Int
,你的程序将会崩溃:
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的主要内容,如果未能解决你的问题,请参考以下文章