编程iOS 开发者

在 Swift 中,subscripting 是什么,如何为自定义结构实现自定义 subscript?

用 Hintsage AI 助手通过面试

答案。

问题背景:

Subscript 是一个机制,可以通过方括号访问集合、结构和其他类型中的值。它在 Swift 中的引入是借鉴了其他语言(例如 Python 中的数组索引,C++/Java 中的 [] 运算符),旨在使处理集合和类似对象的工作更加直观。

问题:

并不是所有类型都默认支持像数组一样的索引访问。当我们有一个带数据的结构或类时,通过熟悉的语法(如 myObject[index])访问元素通常是更合乎逻辑的,而不是通过方法。

解决方案:

Swift 允许为任何用户定义的类型自行实现 subscript。可以创建仅用于读取或同时用于读取和写入的 subscript。以下是实现针对平方矩阵进行操作的 subscript 的结构示例:

struct Matrix { let size: Int private var grid: [Int] init(size: Int) { self.size = size self.grid = Array(repeating: 0, count: size * size) } subscript(row: Int, column: Int) -> Int { get { precondition(indexIsValid(row, column), "索引超出范围") return grid[(row * size) + column] } set { precondition(indexIsValid(row, column), "索引超出范围") grid[(row * size) + column] = newValue } } private func indexIsValid(_ row: Int, _ column: Int) -> Bool { return row >= 0 && row < size && column >= 0 && column < size } } var m = Matrix(size: 3) m[1,2] = 7 print(m[1,2]) // 7

关键特点:

  • Subscript 可以接受任意数量的参数,不仅仅是一个索引
  • 使用关键字 subscript 实现,可以是仅获取或获取/设置
  • 允许为用户定义的集合或结构创建易于使用且可读的 API

陷阱问题。

Subscript 只能用于集合吗?

不,subscript 可以为任何类型实现:结构、类、枚举。内部数据类型不必是集合,主要在于通过 subscript 处理请求的逻辑。

一个类型中可以有多个不同签名的 subscript 吗?

可以,允许在参数签名不同的情况下重载 subscript:

struct Example { subscript(index: Int) -> Int { return index } subscript(key: String) -> String { return key.uppercased() } }

可以将 subscript 作为没有 set 的计算属性使用吗?

可以,subscript 可以仅用于读取(仅获取),那么写入尝试将导致编译错误。

struct ReadOnly { subscript(index: Int) -> Int { index * index } }

常见错误和反模式

  • 缺少索引范围检查(索引超出范围)
  • 违反“单一职责原则”—— subscript 中包含业务逻辑,而不仅仅是数据访问
  • subscript 的签名过于复杂且不明显

生活中的例子

负面案例

在一个大型结构中实现了一个没有范围检查的 subscript,导致在索引不正确时应用崩溃。

优点:

  • 快速原型
  • 代码更少

缺点:

  • 高风险的运行时崩溃
  • 由于错误不明显,调试困难

正面案例

Matrix 通过对索引范围的 precondition 实现。任何错误都会立即捕获,不会进入生产环境。

优点:

  • 输入处理的安全性
  • 诊断和维护简单

缺点:

  • 代码和检查略多,但可靠性更高