专栏 :Swift语言精进之路
编号 :A02 · 系列第 2 篇
字数 :约 4500 字
标签:Swift / 类型系统 / 类型推导 / 元组 / 嵌套类型 / 类型别名
前言
Swift 被很多人称为「类型安全的语言」,但类型安全到底意味着什么?
简单说:编译器在编译阶段就能发现大量错误,而不是等到用户用到那一行代码时才发现。
类型系统是 Swift 最核心的设计决策之一。今天我们从最基础的数据类型开始,理解 Swift 的类型系统是如何工作的,以及它如何帮助我们写出更安全的代码。
一、Swift 类型的核心原则
1.1 类型安全 = 编译时检查
swift
// ❌ 这行代码在 Swift 中无法编译
let age: Int = "twenty-five" // Error: Cannot convert String to Int
// ✅ Swift 的类型是强制的,不能隐式转换
let age: Int = 25
let name: String = "Alice"
Swift 不允许隐式类型转换------这和 JavaScript、Python 完全不同:
swift
// JavaScript(隐式转换)
let result = "5" + 3 // "53" (字符串拼接,行为可能不符合预期)
// Swift(必须显式转换)
let result = Int("5")! + 3 // 8 (明确知道在做什么)
1.2 类型推导:让代码简洁但不牺牲安全
Swift 可以从上下文推导出变量类型,但一旦确定就不能改变:
swift
// Swift 自动推导出 name 是 String
let name = "Alice"
name = "Bob" // ✅ 同一个类型,OK
name = 123 // ❌ Error: Cannot assign Int to String
1.3 基本类型一览
| 类型 | 说明 | 示例 |
|---|---|---|
Int / UInt |
有符号/无符号整数 | 42, -10 |
Double / Float |
浮点数 | 3.14, 1.0e-5 |
Bool |
布尔值 | true, false |
String |
字符串 | "Hello" |
Character |
单个字符 | "A" |
Array<T> |
数组 | [1, 2, 3] |
Dictionary<K,V> |
字典 | ["key": "value"] |
Set<T> |
无序集合 | Set([1, 2, 3]) |
Optional<T> |
可选值 | Int? |
二、整数的精确选择
2.1 Int 的平台相关大小
swift
// Int 的大小根据平台自动选择
// 64位 macOS/iOS: Int = Int64
// 32位平台: Int = Int32
print(Int.min) // -9223372036854775808
print(Int.max) // 9223372036854775807
// 如果需要确定大小,使用明确类型
let i8: Int8 = 127 // -128 ~ 127
let i16: Int16 = 32767 // -32768 ~ 32767
let i32: Int32 = 2147483647
let i64: Int64 = 9223372036854775807
2.2 整数溢出
Swift 对溢出的处理非常严格:
swift
// Debug 模式下:溢出直接崩溃
let maxInt = Int.max
let overflow = maxInt + 1 // ❌ Crash: arithmetic overflow
// Release 模式:可以配置 wrapping
// 编译器选项:-overflow-checks=false 时会 wrap
如果你需要溢出行为,使用显式方法:
swift
let maxInt = Int.max
let wrapped = maxInt &+ 1 // 溢出环绕,不会崩溃
print(wrapped) // -9223372036854775808
三、String 的深层设计
3.1 String 是值类型
这是 Swift 和 Objective-C 最大的设计差异之一:
swift
// Objective-C: NSString 是引用类型
NSString *a = @"Hello";
NSString *b = a; // b 和 a 指向同一个对象
[a uppercaseString]; // 修改的是同一个对象
// Swift: String 是值类型(Copy-on-Write)
var a = "Hello"
var b = a // 值拷贝(实际上 COW 优化,只在修改时才真正拷贝)
b += " World"
print(a) // "Hello" --- a 不受影响
print(b) // "Hello World"
3.2 字符串的字符计算
Swift 的 count 是 Unicode 感知的,不像其他语言直接返回字节数:
swift
let emoji = "👨👩👧👦"
print(emoji.count) // 1(一个家庭 emoji,由多个 Unicode 码点组成)
print(emoji.utf8.count) // 25 bytes(4 * 4 + 4 + 4 = 25)
let chinese = "你好世界"
print(chinese.count) // 4(4 个字符)
print(chinese.utf8.count) // 16(UTF-8 编码)
3.3 字符串的常见操作
swift
let str = "Hello, Swift!"
// 长度
print(str.count) // 13
// 子串(推荐用 Range,使用下标的已废弃)
let index = str.firstIndex(of: ",")!
let substr = str[..<index] // "Hello"
let range = str.index(str.startIndex, offsetBy: 5)..<str.endIndex
_ = str[range] // ", Swift!"
// 分割
let parts = str.split(separator: ",") // ["Hello", " Swift!"]
let trimmed = str.trimmingCharacters(in: .whitespaces)
// 查找和替换
if str.contains("Swift") { ... }
let replaced = str.replacingOccurrences(of: "Swift", with: "Objective-C")
// 插值(类型安全)
let name = "Alice"
let age = 25
let greeting = "Hello, \(name). You are \(age) years old."
print(greeting) // "Hello, Alice. You are 25 years old."
// 多行字符串
let poem = """
床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。
"""
四、元组:组合多个值
4.1 元组的基础用法
元组是 Swift 特有的类型,用于临时组合少量异构值:
swift
// 基本元组
let point = (x: 10, y: 20)
print(point.x) // 10
print(point.y) // 20
// 索引访问
let first = point.0 // 10
let second = point.1 // 20
// 无标签元组
let rgb = (255, 128, 64)
print(rgb.0) // 255
// 函数返回多个值
func divide(_ a: Double, by b: Double) -> (quotient: Double, remainder: Double) {
return (quotient: floor(a / b), remainder: a.truncatingRemainder(dividingBy: b))
}
let result = divide(10.0, by: 3.0)
print("商=\(result.quotient), 余数=\(result.remainder)")
4.2 元组 vs 结构体
| 场景 | 使用元组 | 使用结构体 |
|---|---|---|
| 临时组合 2-3 个值 | ✅ | ❌ |
| 函数返回多个相关值 | ✅ | ❌ |
| 作为字典的 Key | ❌ | ✅(需要 Hashable) |
| 需要方法 | ❌ | ✅ |
| 需要默认值 | ❌ | ✅ |
| 需要遵守协议 | ❌ | ✅ |
swift
// ✅ 临时使用
let httpStatus = (code: 200, message: "OK", isSuccess: true)
// ✅ 作为函数返回类型
func findUser(id: Int) -> (name: String, email: String)? { ... }
// ❌ 长期存储 → 用结构体
struct User {
let id: Int
let name: String
let email: String
}
五、类型别名:让代码更清晰
5.1 基础用法
swift
typealias UserID = String
typealias Money = Decimal
typealias CompletionHandler = (Result<Void, Error>) -> Void
// 在代码中使用
func fetchUser(id: UserID) async throws -> User { ... }
let balance: Money = 1000.50
5.2 典型应用场景
swift
// 网络层:统一错误类型
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
case serverError(Int)
}
typealias NetworkResult<T> = Result<T, NetworkError>
// 并发:统一线程类型
@MainActor func updateUI() { ... }
// 特定业务:让代码自文档化
typealias Celsius = Double
typealias Fahrenheit = Double
func convert(celsius: Celsius) -> Fahrenheit {
return celsius * 9 / 5 + 32
}
六、可选类型:Swift 的安全基石
6.1 可选类型的三种表示
swift
// 三种写法等价
var name: String? = nil
var name1: Optional<String> = nil
var name2: Optional<String>.none = nil
6.2 解包方法对比
| 方法 | 适用场景 | 安全性 |
|---|---|---|
if let / guard let |
确认值存在时使用 | 安全 |
?? 空值合并 |
提供默认值 | 安全 |
! 强制解包 |
确定值一定存在(极不推荐) | 危险 |
?. 可选链 |
安全调用可能为空的方法 | 安全 |
swift
let optionalName: String? = "Alice"
//// 1. guard let --- 提前退出
func greet(_ name: String?) {
guard let name else {
print("No name provided")
return
}
print("Hello, \(name)!") // name 在此处是非可选的
}
//// 2. if let --- 分支处理
if let name = optionalName {
print("Hello, \(name)!")
} else {
print("No name")
}
//// 3. 空值合并 ??
let displayName = optionalName ?? "Guest"
//// 4. 可选链
print(optionalName?.uppercased()) // Optional("ALICE")
print(optionalName?.count) // Optional(5)
//// 5. 多层可选链
struct Person {
var address: Address?
}
struct Address {
var city: City?
}
struct City {
var name: String
}
// 安全访问深层嵌套属性
let cityName = person.address?.city?.name ?? "Unknown"
6.3 隐式解包的坑
swift
// ❌ 危险:value 为 nil 时崩溃
var value: String! = nil
let upper = value.uppercased() // Crash!
// ✅ 正确:先用 guard 检查
var value: String! = fetchValue()
guard let value else { return }
print(value.uppercased())
七、自定义类型:结构体与类
7.1 结构体(推荐优先使用)
swift
struct Point {
var x: Double
var y: Double
// 存储属性
let origin: Point = Point(x: 0, y: 0)
// 计算属性
var distanceFromOrigin: Double {
sqrt(x * x + y * y)
}
// 方法
func distance(to other: Point) -> Double {
sqrt(pow(other.x - x, 2) + pow(other.y - y, 2))
}
// 静态方法
static func origin() -> Point {
Point(x: 0, y: 0)
}
}
// 用法
let p1 = Point(x: 3, y: 4)
let p2 = Point(x: 6, y: 8)
print(p1.distance(to: p2)) // 5.0
7.2 结构体 vs 类
这是 Swift 中最重要的选择之一:
| 特性 | 结构体 | 类 |
|---|---|---|
| 类型 | 值类型(拷贝) | 引用类型(共享) |
| 继承 | 不支持 | 支持 |
| 初始化 | 编译器自动生成成员初始化器 | 必须定义 init |
| 身份比较 | 用 ==(值相等) | 用 ===(同一实例) |
| 适用场景 | 数据载体、轻量对象 | 需要共享状态、需要继承 |
Apple 的建议:默认使用结构体,只有在需要引用语义或继承时才用类。
swift
// ✅ 默认用结构体
struct User {
let id: UUID
var name: String
var email: String
}
// 只有在需要以下特性时才用类:
// - 引用共享(如单例)
// - 继承
// - deinit 清理资源
class DatabaseManager {
static let shared = DatabaseManager() // 单例必须是类
private init() {}
func connect() { ... }
}
八、枚举:Swift 最强大的类型之一
8.1 基础枚举
swift
enum Direction {
case north, south, east, west
}
let dir: Direction = .north
switch dir {
case .north: print("北")
case .south: print("南")
case .east: print("东")
case .west: print("西")
}
8.2 关联值(最重要!)
枚举可以携带数据,这让枚举成为建模状态的最佳工具:
swift
// ❌ 不用枚举的错误方式
class Order {
var status: String // "pending", "paid", "shipped", "delivered"
var trackingNumber: String? // 只有 shipped/delivered 时才有
var cancelReason: String? // 只有 cancelled 时才有
}
// ✅ 用枚举的正确方式
enum OrderStatus {
case pending
case paid
case shipped(trackingNumber: String)
case delivered
case cancelled(reason: String)
}
struct Order {
var status: OrderStatus
var amount: Decimal
}
let order = Order(
status: .shipped(trackingNumber: "SF123456789"),
amount: 99.99
)
// 模式匹配处理
switch order.status {
case .pending:
print("等待付款")
case .paid:
print("已付款")
case .shipped(let tracking):
print("已发货,单号: \(tracking)")
case .delivered:
print("已送达")
case .cancelled(let reason):
print("已取消,原因: \(reason)")
}
8.3 原始值与 CaseIterable
swift
// 原始值枚举
enum HTTPStatus: Int {
case ok = 200
case notFound = 404
case serverError = 500
}
print(HTTPStatus.ok.rawValue) // 200
print(HTTPStatus(rawValue: 200)!) // HTTPStatus.ok
// CaseIterable:枚举所有实例
enum Planet: CaseIterable {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
for planet in Planet.allCases {
print(planet)
}
九、综合示例:定义一个完整的模型
swift
import Foundation
// 货币类型别名
typealias Money = Decimal
// 交易类型枚举
enum TransactionType: String, Codable {
case income = "income"
case expense = "expense"
}
// 交易状态枚举
enum TransactionStatus: Equatable {
case pending
case completed(date: Date)
case failed(reason: String)
}
// 交易记录
struct Transaction: Identifiable, Codable {
let id: UUID
var type: TransactionType
var amount: Money
var category: String
var note: String?
var status: TransactionStatus
let createdAt: Date
var updatedAt: Date
// 计算属性
var displayAmount: String {
let prefix = type == .income ? "+" : "-"
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "CNY"
return prefix + (formatter.string(from: amount as NSDecimalNumber) ?? "¥0.00")
}
// 方法
func isLargeTransaction(threshold: Money = 1000) -> Bool {
amount >= threshold
}
// 静态工厂方法
static func income(amount: Money, category: String, note: String? = nil) -> Transaction {
Transaction(
id: UUID(),
type: .income,
amount: amount,
category: category,
note: note,
status: .pending,
createdAt: Date(),
updatedAt: Date()
)
}
}
// 使用
let salary = Transaction.income(amount: 15000, category: "工资", note: "4月份工资")
print(salary.displayAmount) // "+¥15,000.00"
总结
今天我们学习了 Swift 类型系统的核心概念:
- 类型安全:编译期检查,消灭大量运行时错误
- 类型推导:代码简洁,同时保持类型安全
- String 是值类型:Copy-on-Write 优化,线程安全
- 元组:临时组合少量异构值
- 可选类型:Swift 处理「值可能不存在」的标准方式
- 结构体优先:Apple 推荐的默认选择
- 枚举关联值:建模状态的最佳工具
下一篇文章我们将深入 Swift 的控制流,看看 guard、switch 的模式匹配、以及 for-in 的各种高级用法。
往期回顾:
如果这篇文章对你有帮助,欢迎点赞。你的支持是我持续输出的最大动力。