ProgrammingMid/Senior iOS Developer

What are subscripts in Swift and how can they be overridden or extended? Provide an example of advanced usage.

Pass interviews with Hintsage AI assistant

Answer.

Subscripts in Swift provide a convenient syntax for accessing elements of a collection, container, or user-defined type using indices, similar to arrays and dictionaries. They can be declared and overridden in custom classes, structs, and enums.

Background of the Question

Subscripts were introduced in Swift as a way to unify indexed access to objects, starting with collections and then extending to any user-defined types. The key goal is to enhance expressiveness and readability, turning data access methods into a "natural" syntax that resembles array usage.

Problem

In other languages (e.g. Java), accessing an element of a structure is done through get/set methods, making the code verbose. The quest for better readability and easier data access led to subscripts. In Swift, special attention is required for safe access, getters and setters, possible errors, and optional return values.

Solution

A subscript can be declared in any class, struct, or enum using the subscript keyword. Additionally, multiple overloads by parameters and return types are supported.

Example of a user-defined subscript:

struct Matrix { let rows: Int, columns: Int private var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(repeating: 0.0, count: rows * columns) } subscript(row: Int, column: Int) -> Double { get { precondition(row >= 0 && row < rows) precondition(column >= 0 && column < columns) return grid[(row * columns) + column] } set { precondition(row >= 0 && row < rows) precondition(column >= 0 && column < columns) grid[(row * columns) + column] = newValue } } } var matrix = Matrix(rows: 2, columns: 2) matrix[1, 1] = 3.14 print(matrix[1, 1]) // 3.14

Key Features:

  • Multiple subscripts can be defined with different types and numbers of parameters
  • A subscript may be only get or get/set
  • Subscripts can be used with different index types, even with ranges and strings

Trick Questions.

Can a subscript be declared with only a getter, without a setter?

Yes. If a subscript is needed for read-only access, it is sufficient to implement only get.

struct ReadOnlyArray { private let items = [1, 2, 3] subscript(index: Int) -> Int { get { return items[index] } } }

Is it allowed to declare multiple subscripts with different signatures in the same type?

Yes, subscripts can be overloaded by the number and type of parameters.

struct Collection { subscript(index: Int) -> String { ... } subscript(key: String) -> Int? { ... } }

Can a subscript be declared in a protocol and implemented in a struct/class?

Yes, protocols can require the implementation of a subscript, and concrete types must implement them.

protocol MySubscriptable { subscript(index: Int) -> String { get set } } struct SubData: MySubscriptable { private var items = ["a", "b"] subscript(index: Int) -> String { get { return items[index] } set { items[index] = newValue } } }

Common Mistakes and Anti-Patterns

  • Performing unsafe access without boundary checks (precondition, bounds check)
  • Data type errors for subscript parameters
  • Excessive use of subscripts without necessity, where methods would be more appropriate

Real-life Example

Negative Case

A developer implemented a subscript without boundary checking. As a result, accessing a non-existent index caused the application to crash with a runtime error.

Pros: The code became more concise.

Cons: The application started to crash due to unexpected errors.

Positive Case

Implementation of a subscript with precondition checks and the ability to return optional values or throw errors.

Pros: The code became safer for users, and errors can be handled.

Cons: Additional logic for error handling must be laid out, and behavior for invalid indices must be considered.