陈斌彬的技术博客

Stay foolish,stay hungry

Swift-溢出

对于 Mac 开发,我们早已步入了 64 位时代,而对 iOS 来说,64 位的乐章才刚刚开始。在今后一段时间内,我们都需要面临同时为 32 位和 64 位的设备进行开发的局面。由于这个条件所导致的最直接的一个结果就是数字类型的区别。

最简单的例子,在 Swift 中我们一般简单地使用 Int 来表示整数,在 iPhone 5 和以下的设备中,这个类型其实等同于 Int32,而在 64 位设备中表示的是 Int64 (这点和 Objective-C 中的 NSInteger 表现是完全一样的,事实上,在 Swift 中 NSInteger 只是一个 Int 的 typealias。这就意味着,我们在开发的时候必须考虑同样的代码在不同平台上的表现差异,比如下面的这段计算在 32 位设备上和 64 位设备上的表现就完全不同:

class MyClass {
    var a: Int = 1
    func method() {
        a = a * 100000
        a = a * 100000
        a = a * 100000
        print(a)
    }
}

MyClass().method()

// 64 位环境 (iPhone 5s 及以上)
// 1,000,000,000,000,000

// 32 位环境 (iPhone 5c 及以下)
// 崩溃

因为 32 位的 Int 的最大值为 2,147,483,647,这个方法的计算已经超过了 Int32 的最大值。和其他一些编程语言的处理不同的是,Swift 在溢出的时候选择了让程序直接崩溃而不是截掉超出的部分,这也是一种安全性的表现。

另外,编译器其实已经足够智能,可以帮助我们在编译的时候就发现某些必然的错误。比如:

func method() {
    var max = Int.max
    max = max + 1
}

这种常量运算在编译时就进行了,发现计算溢出后编译无法通过。

在存在溢出可能性的地方,第一选择当然是使用更大空间的类型来表示,比如将原来的 Int32 显式地声明为 Int64。如果 64 位整数还无法满足需求的话,我们也可以考虑使用两个 Int64 来软件实现 Int128 (据我所知现在还没有面向消费领域的 128 位的电子设备) 的行为。

最后,如果我们想要其他编程语言那样的对溢出处理温柔一些,不是让程序崩溃,而是简单地从高位截断的话,可以使用溢出处理的运算符,在 Swift 中,我们可以使用以下这五个带有 & 的操作符,这样 Swift 就会忽略掉溢出的错误:

  • 溢出加法 (&+)
  • 溢出减法 (&-)
  • 溢出乘法 (&*)
  • 溢出除法 (&/)
  • 溢出求模 (&%)

这样处理的结果:

var max = Int.max
max = max &+ 1

// 64 位系统下
// max = -9,223,372,036,854,775,808