Swift 的 Subscripts 下標可以讓我們自己實作像是 Array 或 Dictionary 用中括號來取值的語法。
Array 與 Dictionary 的 Subscripts 下標語法
我們最常見也習慣的 Subscripts 下標語法,就出現在要存取 Array 中的某個元素,或是要透過 key 來存取 Dictionary 中的某個值時,如 someArray[index]
或 Dictionary[key]
,像這樣使用中括號並透過索引的方式來存取值,就是所謂的「Subscripts 下標語法」。
Array 與 Dictionary 之所以能直接在後方使用中括號,就是因為它們都有實作 Subscripts 下標,這能讓我們用較精簡的方式來取值或設定值,而非透過 get 與 set 的 func。而其實,我們也能為自己定義的型別實作 Subscripts 下標。
定義 Swift 的 Subscripts 下標語法
class、struct 和 enum 都能定義 Subscripts 下標,而且也能定義不只一個,就像 func 可以 overload 重載一樣。除了可以定義多個 Subscripts 下標,每個下標也能吃超過一個輸入!
可讀可寫的 Subscripts 下標
定義 Subscripts 的寫法和定義 computed property 類似:
subscript(index: Int) -> Int {
get {
// 回傳特定的值
}
set(newValue) {
// 執行特定的資料設定
}
}
我們會使用 subscript
這個關鍵字,並決定要有幾個輸入、回傳什麼,就像定義 func 一樣。但內容跟 func 不一樣,Subscripts 可以決定要「read-write 可讀可寫」或是「read-only 僅可讀」,就像 computed property 有 getter 與 setter 一樣,而這裡的 setter 也是可以在小括號裡幫 newValue 取名字,如果不想取的話,則可以省略小括號,並使用預設的「newValue」。
僅可讀的 Subscripts 下標
如果是定義僅可讀 read-only 的 Subscripts 下標,可以省略 get
與 set
:
subscript(index: Int) -> Int {
// 回傳特定的值
}
這裡舉一個實際使用 Subscripts 下標的例子:
struct TimesTable {
/// 乘數
let multiplier: Int
/// index 為被乘數
subscript(index: Int) -> Int {
return index * multiplier
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("6 乘以 3 等於 \(threeTimesTable[6])")
在型別裡定義了 Subscripts 之後,我們就能直接在變數後面使用中括號來呼叫 subscript
。
注意
這裡可以發現,我們的 Subscripts 定義了參數名稱「index」,但和 func 不一樣,在使用中括號時,不需要指名參數名稱,可以直接傳入索引。
自動省略 Argument 的 Subscripts
因為 Swift 的 Subscripts 會自動省略 Argument,所以上面的例子相當於:
subscript(_ index: Int) -> Int {
return index * multiplier
}
而如果在使用中括號時,希望能看到 Argument 的話,則可以另外命名:
subscript(multiplicand index: Int) -> Int {
return index * multiplier
}
使用上就會像這樣:
let threeTimesTable = TimesTable(multiplier: 3)
print("6 乘以 3 等於 \(threeTimesTable[multiplicand: 6])")
備註
我們有另外一篇文章詳細說明了 Func 中,Argument 與 Parameter 的差異。如果想了解更多的話,可以點擊這裡。
Swift Dictionary 的 Subscripts 實作邏輯
Swift Dictionary 用 Subscripts 讓我們可以用 key-value 的方式來存取資料。在 get
的實作上,Swift 會回傳 Optional
,因為我們並不能保證每個 key 值都有對應的 value。而刪除時,就是把 key 對應的 value 改成 nil
。
多個輸入的 Subscripts 下標
雖然平常看到的 Subscripts 下標都是只吃一個輸入而已,但我們仍可以根據需求,實作多個輸入的 Subscripts。就像 func 一樣,可以有多個輸入、不同的輸入型別、不同的回傳型別,但跟 func 唯一不一樣的是不能使用 inout
參數。
這裡用 Subscripts 實作一個吃兩個輸入的二維陣列:
/// 二維陣列
struct Matrix {
/// 行與列
let rows: Int, columns: Int
/// 裝所有元素的 Array
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
// 陣列剛建出來時,各個元素的預設值為 0.0
grid = Array(repeating: 0.0, count: rows * columns)
}
/// 欲存取的 Index 是否有效
/// - Parameters:
/// - row: 欲存取 Index 的行
/// - column: 欲存取 Index 的列
/// - Returns: 是否有效
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
}
}
}
因為這是一個二維陣列,所以定義了一個能吃兩個輸入的 Subscripts,來讓我們指定要存取哪一行哪一列的元素。
在使用這個二維陣列 Matrix
之前,就要先建一個實體。
var matrix = Matrix(rows: 2, columns: 2)
這裡建一個 2×2 的二維陣列,當我們想透過 Subscripts 下標來存取值時,會這樣寫:
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
let someValue = matrix[2, 2] // 這會觸發 assert:Index out of range
當 Subscripts 有兩個以上的輸入時,會寫在同一個中括號裡,而非多個中括號。
型別 Subscripts
和常數、變數、方法一樣,如果我們想直接在型別上使用下標語法,而不需先建一個實體的話,也能使用關鍵字 static
。
/// 星球
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
/// Type Subscripts
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
關於 XcodeProject
XcodeProject 創立於 2023,致力於協助開發者探索 Apple 的創新世界,學習在 iOS、iPadOS、macOS、tvOS、visionOS 與 watchOS 上開發 App,發現眾多技術與框架,讓開發者獲得更多能力。