ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Swift 常量详解:let 与 const 的正确使用方式

Swift 常量详解:let 与 const 的正确使用方式

1. 引言

在 Swift 编程语言中,常量是构建安全、可预测代码的基础。与变量不同,常量一旦赋值便不可更改,这不仅能防止意外修改,还能让代码的意图更加清晰。本文将深入讲解 Swift 常量的定义、使用场景以及最佳实践,并通过丰富的代码实例帮助你快速掌握。

2. 什么是常量

常量是指在程序运行过程中值保持不变的量。Swift 使用let关键字来声明常量。一旦为常量赋值,就不能再修改它的值。这种不可变性是 Swift 类型安全体系的重要组成部分。

与变量(使用var声明)相比,常量具有以下优势:

  • 安全性:防止代码在无意中修改重要数据。
  • 可读性:明确告诉阅读者该值不会变化,降低理解成本。
  • 性能优化:编译器可以对常量进行更多优化,提升运行效率。

3. 常量的基本用法

使用let关键字声明常量,语法非常简单:

let constantName: Type = value

其中,类型标注(: Type)是可选的,Swift 支持类型推断。下面是一些基本示例:

// 声明一个整数常量 let maxAttempts = 5 // 声明一个字符串常量 let appName = "MySwiftApp" // 声明一个浮点常量 let pi = 3.14159 // 显式指定类型 let isEnabled: Bool = true // 打印常量值 print("应用名称:\(appName)") print("最大尝试次数:\(maxAttempts)")

4. 常量的类型推断

Swift 强大的类型推断机制允许你在声明常量时省略类型标注。编译器会根据赋值的字面量自动推断常量的类型:

// 整数类型推断为 Int let age = 25 // 浮点数类型推断为 Double let height = 1.75 // 字符串类型推断为 String let city = "北京" // 布尔类型推断为 Bool let isLoggedIn = true // 使用 type(of:) 查看推断出的类型 print(type(of: age)) // 输出:Int print(type(of: height)) // 输出:Double print(type(of: city)) // 输出:String print(type(of: isLoggedIn)) // 输出:Bool

5. 常量的不可变性

常量的核心特性是不可变性。一旦赋值,任何试图修改常量的操作都会导致编译错误。下面通过代码演示:

let fixedValue = 100 // 下面的代码会报错:Cannot assign to value: 'fixedValue' is a 'let' constant // fixedValue = 200 // 正确的做法:如果需要可变值,使用 var var mutableValue = 100 mutableValue = 200 print("可变值:\(mutableValue)")

编译器会在编译阶段就拦截对常量的修改,这有助于在开发早期发现潜在问题。

6. 常量与变量的选择

在 Swift 开发中,一个重要的原则是:优先使用常量,只有在值确实需要变化时才使用变量。下面通过对比示例来说明:

// 不推荐:使用变量声明不会变化的值 var userName = "Alice" var maxRetryCount = 3 // 推荐:使用常量声明不会变化的值 let userName = "Alice" let maxRetryCount = 3 // 需要变化的值才使用变量 var currentScore = 0 currentScore += 10 print("当前得分:\(currentScore)")

遵循这一原则,可以让代码更加健壮,减少因意外赋值导致的 bug。

7. 常量的作用域

常量可以定义在全局作用域、函数作用域或类/结构体内部。不同作用域的常量具有不同的生命周期和访问范围:

// 全局常量 let globalConstant = "全局常量" func demoScope() { // 函数内局部常量 let localConstant = "局部常量" print(localConstant) print(globalConstant) // 可以访问全局常量 } class MyClass { // 类型属性(类常量) static let classConstant = "类常量" // 实例属性(实例常量) let instanceConstant: String init(value: String) { self.instanceConstant = value } } demoScope() print(MyClass.classConstant) let obj = MyClass(value: "实例常量") print(obj.instanceConstant)

8. 常量与集合类型

当常量是数组、字典或集合时,需要注意:常量本身不可重新赋值,但其内部元素是否可变取决于集合的类型。Swift 中的Array、Dictionary和Set是值类型,因此用let声明的集合,其元素也不可修改:

// 常量数组:元素不可修改 let fruits = ["苹果", "香蕉", "橙子"] // fruits.append("葡萄") // 错误:Cannot use mutating member on immutable value // fruits[0] = "西瓜" // 错误:Cannot assign through subscript // 常量字典:键值对不可修改 let scores = ["数学": 95, "语文": 88] // scores["英语"] = 90 // 错误 // 常量集合 let uniqueNumbers: Set<Int> = [1, 2, 3] // uniqueNumbers.insert(4) // 错误 // 如果需要可变集合,使用 var var mutableFruits = ["苹果", "香蕉"] mutableFruits.append("葡萄") print(mutableFruits)

9. 常量与引用类型

当常量指向的是引用类型(如类实例)时,常量本身不可重新赋值,但通过该常量可以修改对象的属性或调用修改方法。这是因为引用类型是共享的:

class Counter { var count = 0 } // 常量指向类实例 let counter = Counter() counter.count = 10 // 允许:修改对象的属性 print("计数:\(counter.count)") // counter = Counter() // 错误:Cannot assign to value: 'counter' is a 'let' constant // 对比:结构体是值类型,let 声明的结构体属性不可修改 struct Point { var x: Int var y: Int } let point = Point(x: 1, y: 2) // point.x = 5 // 错误:Cannot assign to property: 'point' is a 'let' constant

10. 常量与可选类型

常量同样可以声明为可选类型。可选常量在赋值前默认为nil,赋值后不可再修改:

// 声明可选常量 let optionalValue: Int? = nil // optionalValue = 10 // 错误:Cannot assign to value: 'optionalValue' is a 'let' constant // 在初始化时直接赋值 let initializedOptional: String? = "Hello" print(initializedOptional ?? "默认值") // 可选常量配合 if let 使用 let username: String? = "SwiftUser" if let name = username { print("用户名:\(name)") } else { print("用户名为空") }

11. 常量与元组

元组可以将多个值组合成一个复合值。使用let声明的元组,其元素同样不可修改:

// 声明元组常量 let coordinates = (x: 10, y: 20) print("x: \(coordinates.x), y: \(coordinates.y)") // 元组解构 let (width, height) = (1920, 1080) print("分辨率:\(width) x \(height)") // 元组作为函数返回值 func getMinMax() -> (min: Int, max: Int) { return (1, 100) } let result = getMinMax() print("最小值:\(result.min),最大值:\(result.max)") // 修改元组元素会报错 // coordinates.x = 30 // 错误:Cannot assign to property: 'coordinates' is a 'let' constant

12. 常量与计算属性

虽然常量本身不可变,但你可以使用计算属性来动态获取值。计算属性不存储值,而是每次访问时计算:

struct Circle { let radius: Double // 计算属性:每次访问时计算 var area: Double { return Double.pi * radius * radius } var circumference: Double { return 2 * Double.pi * radius } } let circle = Circle(radius: 5.0) print("面积:\(circle.area)") print("周长:\(circle.circumference)") // 计算属性是只读的,不能赋值 // circle.area = 100 // 错误:Cannot assign to property: 'area' is a get-only property

13. 常量与函数参数

在 Swift 中,函数参数默认是常量。这意味着在函数体内不能修改参数的值:

func greet(name: String) { // name 是常量,不能修改 // name = "新名字" // 错误:Cannot assign to value: 'name' is a 'let' constant print("你好,\(name)!") } // 如果需要修改参数,使用 inout 参数 func increment(_ number: inout Int) { number += 1 } var count = 10 increment(&count) print("递增后:\(count)") // 调用函数 greet(name: "Swift")

14. 常量与闭包

闭包可以捕获常量。被捕获的常量在闭包内保持其值,即使闭包在常量作用域之外被调用:

func makeIncrementer(amount: Int) -> () -> Int { var total = 0 // 捕获 amount 常量 return { total += amount return total } } let incrementByTen = makeIncrementer(amount: 10) print(incrementByTen()) // 输出:10 print(incrementByTen()) // 输出:20 print(incrementByTen()) // 输出:30 // 常量捕获示例 let baseValue = 100 let addBase: (Int) -> Int = { value in return value + baseValue } print(addBase(50)) // 输出:150

15. 常量与枚举

枚举的关联值和原始值经常与常量配合使用。枚举本身是值类型,用let声明的枚举常量不可修改:

enum Direction { case north, south, east, west } enum StatusCode: Int { case success = 200 case notFound = 404 case serverError = 500 } // 枚举常量 let direction = Direction.north let status = StatusCode.success // 使用 switch 匹配枚举 switch direction { case .north: print("向北") case .south: print("向南") case .east: print("向东") case .west: print("向西") } // 枚举原始值 print("状态码:\(status.rawValue)") // 枚举关联值 enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String) } let productBarcode = Barcode.upc(8, 85909, 51226, 3) switch productBarcode { case .upc(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(check)") case .qrCode(let code): print("QR code: \(code)") }

16. 常量与泛型

在泛型编程中,常量同样扮演重要角色。泛型函数或类型中的常量可以保持类型安全:

// 泛型函数中的常量 func findMax<T: Comparable>(in array: [T]) -> T? { guard let first = array.first else { return nil } var maxValue = first for item in array { if item > maxValue { maxValue = item } } return maxValue } let numbers = [3, 7, 1, 9, 5] if let maxNumber = findMax(in: numbers) { print("最大值:\(maxNumber)") } let strings = ["apple", "banana", "cherry"] if let maxString = findMax(in: strings) { print("最大字符串:\(maxString)") } // 泛型结构体中的常量 struct Stack<Element> { private var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element? { return items.popLast() } var count: Int { return items.count } } var stack = Stack<Int>() stack.push(1) stack.push(2) stack.push(3) print("栈元素个数:\(stack.count)") print("弹出:\(stack.pop() ?? -1)")

17. 常量与协议

协议中可以使用常量要求,但通常通过计算属性或静态属性来实现。下面展示如何在协议中使用常量:

protocol Vehicle { var maxSpeed: Int { get } var name: String { get } } struct Car: Vehicle { // 使用常量满足协议要求 let maxSpeed: Int let name: String } struct Bicycle: Vehicle { let maxSpeed: Int let name: String } let car = Car(maxSpeed: 200, name: "跑车") let bicycle = Bicycle(maxSpeed: 30, name: "自行车") func describe(vehicle: Vehicle) { print("\(vehicle.name) 的最高速度是 \(vehicle.maxSpeed) km/h") } describe(vehicle: car) describe(vehicle: bicycle) // 协议中的静态常量 protocol AppConfig { static var appName: String { get } static var version: String { get } } struct DevelopmentConfig: AppConfig { static let appName = "DevApp" static let version = "1.0.0-beta" } struct ProductionConfig: AppConfig { static let appName = "ProductionApp" static let version = "1.0.0" } print("开发环境:\(DevelopmentConfig.appName) \(DevelopmentConfig.version)") print("生产环境:\(ProductionConfig.appName) \(ProductionConfig.version)")

18. 常量与错误处理

在错误处理中,常量常用于定义错误码和错误信息,确保错误处理的稳定性和可读性:

// 定义错误类型 enum NetworkError: Error { case badURL case timeout case serverError(code: Int) } // 使用常量定义错误信息 let errorMessages: [NetworkError: String] = [ .badURL: "无效的 URL", .timeout: "请求超时", .serverError(code: 500): "服务器内部错误" ] func fetchData(from urlString: String) throws -> String { // 使用常量校验 URL let validURLPrefix = "https://" guard urlString.hasPrefix(validURLPrefix) else { throw NetworkError.badURL } // 模拟网络请求 let simulatedResponse = "模拟数据" return simulatedResponse } // 错误处理示例 do { let data = try fetchData(from: "https://example.com") print("获取数据:\(data)") } catch NetworkError.badURL { print("错误:无效的 URL") } catch NetworkError.timeout { print("错误:请求超时") } catch { print("未知错误:\(error)") }

19. 常量与并发编程

在 Swift 并发编程中,常量对于保证线程安全尤为重要。不可变数据天然适合在多线程环境中共享:

import Foundation // 使用常量定义线程安全的配置 let threadSafeConfig = [ "timeout": 30, "maxRetries": 3, "enableLogging": true ] // 使用 DispatchQueue 模拟并发访问 let queue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent) for index in 0..<5 { queue.async { // 并发读取常量是安全的
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进