写Swift代码的第一天起,你就离不开类型系统。Swift是一门强类型语言,每一个变量、常量、函数参数都有明确的类型,编译器会在编译阶段做大量检查,把很多运行时错误提前拦截下来。也正因为如此,搞清楚每种数据类型的特性、适用场景和潜在陷阱,是写好Swift代码的基本功。本文把Swift常用的数据类型逐一拆开讲解,并给出选择建议和避坑经验。

基础类型全览:整数、浮点数、布尔与字符
Swift的基础数值类型分为整数和浮点数两大类。整数方面,Swift提供了Int和UInt,以及Int8、Int16、Int32、Int64等固定长度的变体。默认情况下,直接写10这样的字面量,编译器推断为Int。官方建议非常明确:除非有特殊需求(比如处理固定格式的二进制数据),否则一律使用Int,即使你知道某个值永远不会为负。Int在64位平台上是64位,与系统原生字长一致,性能最好,而且和标准库、第三方库的交互最顺畅。用UInt反而会带来类型转换的麻烦,得不偿失。
浮点数有Double(64位双精度)和Float(32位单精度)两种。两者的精度差异很大:Double能提供至少15位十进制有效数字,Float只有6位左右。除非是在对内存极度敏感的场景(比如存大量顶点数据、做图形处理),否则优先选Double。Swift对字面量的推断规则是:写3.14会被推断为Double而不是Float,这一点和很多语言不同,刚上手时容易混淆。
布尔类型Bool只有true和false两个值。Swift在这点上非常严格,条件判断里不能像C语言那样用非零整数代替布尔值,if count { }这种写法直接编译不过,必须老老实实写if count > 0 { }。字符类型Character表示单个Unicode字符,一个汉字、一个emoji都是一个Character,这一点后面讲字符串时会展开。
let integerValue: Int = 42 // 默认推荐使用 Int let pi: Double = 3.14159 // 浮点字面量默认推断为 Double let shortFloat: Float = 1.5 // 显式声明 Float let isReady: Bool = true let firstChar: Character = "中" // 一个汉字也是一个字符 // 不同整数类型之间不能隐式转换,必须显式转换 let a: Int16 = 100 let b: Int32 = 200 let sum = Int32(a) + b // 必须转换成同一类型才能运算
字符串与可选类型:最常用也最容易踩坑的两类
Swift的String是值类型,赋值和传参时会发生拷贝。乍一听好像性能很差,实际上Swift标准库做了写时复制(Copy on Write)优化:只有当字符串真正被修改时才会复制底层存储,日常使用完全不用担心性能问题。字符串遍历时要特别注意,String的底层是Unicode编码集合,一个Character可能由多个Unicode标量组成。比如emoji表情,用string.count数出来是1,但用string.utf8.count数出来可能是4。取子串时,string[index]下标不能用整数,必须用String.Index,这是因为不同字符占用的字节数不同,无法做到O(1)的整数定位。
可选类型Optional是Swift安全性的核心设计。所有类型的默认值都不是nil,只有声明为可选类型(Int?、String?)才能持有nil。解包方式有几种:强制解包!最危险,值为nil时直接崩溃,线上代码要尽量避免;if let和guard let是安全的首选,能保证解包成功后在作用域内拿到确定值;空合运算符??适合给默认值的场景。Swift 5.7之后还引入了if let x的简写形式,可以省略重复的变量名,代码更清爽。
var name: String? = "Swift"
// 不推荐:强制解包,name 为 nil 时崩溃
// let upper = name!.uppercased()
// 推荐:if let 安全解包
if let name = name {
print("名字是 \(name)")
}
// 推荐:guard let 提前退出,减少嵌套
func greet(_ name: String?) {
guard let name else { return }
print("你好,\(name)")
}
// 推荐:空合运算符给默认值
let display = name ?? "匿名用户"
// 字符串下标必须用 String.Index
let str = "Hello, Swift"
let index = str.index(str.startIndex, offsetBy: 7)
print(str[index...]) // 输出 Swift集合类型:数组、字典与集合的选择与性能
Swift提供了三大集合类型:Array(有序数组)、Dictionary(键值对字典)和Set(无序不重复集合)。选择逻辑很清晰:需要保持顺序、按下标访问就用数组;需要按key快速查找就用字典;只需要判断存在性、要去重就用Set。性能上,数组末尾追加是均摊O(1),中间插入删除是O(n);字典和Set的查找、插入、删除平均都是O(1),但要求元素遵守Hashable协议。字典还有一个特性:dict[key]返回的是可选值,因为key可能不存在,这也是类型安全设计的体现。
使用数组时最常见的崩溃就是越界访问。array[10]在长度只有5时不会返回nil,而是直接崩溃。安全的做法是先判断indices.contains(index),或者自己封装一个返回可选值的下标扩展。另外要注意,Array和String一样是值类型,把数组赋值给另一个变量后再修改,不会影响原数组,这与Objective-C的NSMutableArray引用语义完全不同,从OC转过来的开发者要特别留神。
var numbers = [3, 1, 4, 1, 5]
// 越界访问会直接崩溃
// let x = numbers[10]
// 安全写法:先判断下标是否有效
if numbers.indices.contains(3) {
print(numbers[3])
}
var userInfo: [String: Any] = ["name": "Tom", "age": 25]
let name = userInfo["name"] as? String ?? "未知" // 字典取值是可选类型
// Set 去重很方便
let unique = Set(numbers)
print(unique.count) // 输出 4,重复的 1 只保留一个避坑清单:开发中高频出现的几个问题
第一坑是整数溢出。Swift的整数运算默认不允��溢出,超出范围会直接崩溃。如果确实需要环绕运算(比如哈希、加密场景),要用&+、&-、&*这几个溢出运算符。第二坑是浮点数比较。因为二进制无法精确表示某些十进制小数,0.1 + 0.2 == 0.3的结果是false。比较浮点数应该用误差范围判断:两个数差的绝对值小于某个极小值(如0.000001)就认为相等。涉及金额计算时更要注意,建议把金额转换成整数(以分为单位)再运算,或者使用Decimal类型。
第三坑是类型推断与Any的滥用。Any能装任何类型的值,看似方便,实际上丢掉了类型安全,取值时还得强制转换,出错概率大增。能用具体类型或泛型就用它们,Any只在与OC交互或解析无法预知结构的JSON时才考虑使用。第四坑是元组虽然方便打包多个值,但它的元素没有属性名语义,超过两个元素后可读性下降明显,这时候应该定义struct代替。好的类型选择不只是让编译通过,更是让代码一年后还能被自己看懂。把上面这些规则沉淀成习惯,Swift的类型系统就会从约束变成你最有力的工具。
// 坑一:浮点数比较
let a = 0.1 + 0.2
print(a == 0.3) // false
// 正确做法:误差范围比较
func almostEqual(_ x: Double, _ y: Double) -> Bool {
abs(x - y) < 0.000001
}
print(almostEqual(a, 0.3)) // true
// 坑二:溢出运算要用溢出运算符
let maxInt = Int.max
// let overflow = maxInt + 1 // 崩溃
let wrapped = maxInt &+ 1 // 环绕,得到 Int.min
// 金额计算建议用整数表示分
let priceInCents = 1990 // 19.90 元
let total = priceInCents * 3 // 精确无误总结
Swift的数据类型体系并不复杂,但细节很多。记住几条主线:数值默认用Int和Double;字符串是值类型且对Unicode友好;可选类型坚持用安全解包;集合按需求在数组、字典、Set之间选择;浮点数和金额计算要格外小心精度问题。把这些要点收藏起来,遇到疑问时翻一翻,能省下不少排查错误的时间。
常见问题
Swift中Int和UInt应该如何选择?
默认一律使用Int,即使值不会为负。UInt只在处理底层内存布局、外部二进制协议等特殊场景才需要,混用会带来频繁的类型转换。
Swift的String为什么不能用整数下标取字符?
因为Swift字符串按Unicode存储,每个字符占用的字节数不同,整数下标无法保证O(1)定位,所以必须使用String.Index配合index(_:offsetBy:)来定位。
可选类型的强制解包什么时候可以用?h3>
只在两种情况下可以考虑:一是能百分百确定值不为nil(比如刚初始化赋值),二是单元测试代码中。生产代码中优先使用if let、guard let或??,强制解包是线上崩溃的高发源头。