Swift 中的 下标(Subscripts) 是一种强大的语法糖,允许你使用 object[key] 的形式自定义访问逻辑。它是 Swift 类型(class、struct、enum)的一种特殊方法,常用于集合、字典、矩阵等需要“索引访问”的场景。
一、基本语法
subscript(参数列表) -> 返回类型 {
get {
// 返回一个值
}
set(新值) {
// 将新值存储到对应位置
// newValue 是默认参数名
}
}
二、核心示例
1. 简单一维数组封装
struct NumberList {
private var array: [Int] = []
// 初始化
init(count: Int, value: Int) {
array = Array(repeating: value, count: count)
}
// 下标:读写
subscript(index: Int) -> Int {
get {
guard index >= 0 && index < array.count else {
fatalError("索引越界")
}
return array[index]
}
set {
guard index >= 0 && index < array.count else {
fatalError("索引越界")
}
array[index] = newValue
}
}
}
var list = NumberList(count: 5, value: 0)
list[0] = 10
list[2] = 30
print(list[2]) // 30
2. 二维矩阵(多参数下标)
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
// 计算一维索引
private func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
// 多参数下标
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
var matrix = Matrix(rows: 3, columns: 3)
matrix[0, 1] = 3.14
matrix[2, 2] = 9.9
print(matrix[0, 1]) // 3.14
3. 只读下标(省略 set)
struct WeekDays {
private let days = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"]
subscript(index: Int) -> String {
get {
return days[index % days.count] // 循环访问
}
// 无 set → 只读
}
}
let week = WeekDays()
print(week[0]) // Sunday
print(week[7]) // Sunday(循环)
print(week[100]) // Tuesday
4. 带默认值的下标(安全访问)
struct SafeArray<T> {
private var storage: [T]
init(_ elements: T...) {
self.storage = elements
}
subscript(safe index: Int) -> T? {
return indices ~= index ? storage[index] : nil
}
}
let numbers = SafeArray(1, 2, 3, 4, 5)
print(numbers[safe: 2]) // Optional(3)
print(numbers[safe: 10]) // nil
5. 字典式下标(键值访问)
struct DictionaryWrapper {
private var dict: [String: String] = [:]
subscript(key: String) -> String? {
get { dict[key] }
set { dict[key] = newValue }
}
// 默认值下标
subscript(key: String, default defaultValue: String) -> String {
get { dict[key] ?? defaultValue }
set { dict[key] = newValue }
}
}
var wrapper = DictionaryWrapper()
wrapper["name"] = "Alice"
print(wrapper["name"]) // Optional("Alice")
print(wrapper["age", default: "0"]) // 0
6. 类型下标(Type Subscript)
enum HTTPStatus {
case ok, notFound, serverError
static subscript(code: Int) -> HTTPStatus? {
switch code {
case 200: return .ok
case 404: return .notFound
case 500...: return .serverError
default: return nil
}
}
}
print(HTTPStatus[200]) // Optional(HTTPStatus.ok)
print(HTTPStatus[404]) // Optional(HTTPStatus.notFound)
三、下标重载(Overloading)
可以根据参数数量、类型、标签重载多个下标。
struct DataStore {
private var intData: [Int] = []
private var stringData: [String] = []
// 按索引访问 Int
subscript(index: Int) -> Int {
return intData[index]
}
// 按索引访问 String
subscript(index: Int) -> String {
return stringData[index]
}
// 按键访问(泛型)
subscript<T>(key: String) -> T? {
fatalError("未实现")
}
}
编译器根据调用上下文自动选择正确版本。
四、进阶用法
1. 下标 + inout
struct MutableBuffer {
var buffer: [Int] = [1, 2, 3]
subscript(index: Int) -> inout Int {
get { &buffer[index] }
// 无 set,inout 自动处理
}
}
var buf = MutableBuffer()
buf[1] += 10 // 直接修改原始数据
print(buf.buffer) // [1, 12, 3]
2. 下标 + 泛型
struct GenericCollection<T> {
private var items: [T] = []
mutating func add(_ item: T) {
items.append(item)
}
subscript(index: Int) -> T {
return items[index]
}
}
var strings = GenericCollection<String>()
strings.add("A")
strings.add("B")
print(strings[1]) // B
3. 下标 + 协议
protocol Subscriptable {
subscript(index: Int) -> String { get }
}
struct MyData: Subscriptable {
subscript(index: Int) -> String {
return "Item \(index)"
}
}
func printItem(_ obj: some Subscriptable, at index: Int) {
print(obj[index])
}
printItem(MyData(), at: 5) // Item 5
五、最佳实践总结
| 场景 | 推荐写法 |
|---|---|
| 数组/集合封装 | subscript(index: Int) |
| 安全访问 | subscript(safe index: Int) -> T? |
| 字典默认值 | subscript(key:default:) |
| 矩阵/表格 | subscript(row:column:) |
| 只读访问 | 省略 set |
| 类型索引 | static subscript(...) |
| 边界检查 | assert / guard / fatalError |
六、常见面试题
- 下标和方法有何不同?
→ 下标是语法糖,本质是get/set方法,但调用更简洁。 - 下标可以有多个参数吗?
→ 可以,数量和类型不限。 - 下标可以重载吗?
→ 可以,基于参数签名。 - 下标能抛出错误吗?
→ 可以,但需在调用处try:
subscript(index: Int) throws -> Int { ... }
try obj[0]
- 下标能异步吗?
→ 可以(Swift 5.5+):
subscript(index: Int) async -> Data { ... }
await obj[0]
七、小技巧
// 1. 链式下标(结合计算属性)
struct Grid {
var data: [[Int]] = [[1,2],[3,4]]
subscript(row: Int) -> [Int] {
return data[row]
}
}
let grid = Grid()
print(grid[0][1]) // 2
// 2. 动态成员下标(@dynamicMemberLookup)
@dynamicMemberLookup
struct JSON {
subscript(dynamicMember key: String) -> Any? {
return [:][key]
}
}
// 3. 范围下标(自定义 Range)
struct Text {
var content = "Hello, World!"
subscript(r: Range<Int>) -> String {
let start = content.index(content.startIndex, offsetBy: r.lowerBound)
let end = content.index(content.startIndex, offsetBy: r.upperBound)
return String(content[start..<end])
}
}
let text = Text()
print(text[0..<5]) // Hello
八、高级话题(可继续提问)
- 下标与
KeyPath动态访问 - 下标性能优化(Copy-on-Write)
- SwiftUI 中的
@ViewBuilder下标 - 下标与
Collection协议 - 自定义
[]运算符(Operator Overloading)
需要完整项目实战(如自定义 SafeDictionary)、性能对比、或与 Python 字典下标对比?欢迎继续提问!