참고사이트:
The Swift Programming Language
클래스, 구조체, 열거형에서 subscript를 정의해 사용할 수 있다. subscript란 컬렉션, 리스트, 시퀀스 등의 member element에 간단하게 접근할 수 있는 문법이다. subscript를 사용하면 특정한 메소드 없이 값을 가져오거나 할당할 수 있다. 예를 들어 배열의 Array[index]나 Dictionary[key] 처럼 element를 접근한다.
하나의 타입에 여러개의 subscript를 정의할 수 있고 overload(오버로드)도 가능하다. 그리고 단일 인자 값 뿐만 아니라, 필요에 따라 복수 인자 값을 사용할 수 있다.
서브스크립트의 문법은 인스턴스 메소드와 computed property의 문법과 비슷하다. subscript키워드를 사용하여 서브스크립트를 정의하고 인스턴스 메소드처럼 1개 이상의 입력 파라미터와 반환 값을 가진다. 인스턴스와 다르게, 서브스크립트는 read-writ 혹은 read-only만 가능하다. 이는 computed propertiy(계산된 프로퍼티)의 방식처럼 getter와 setter에 의해서 수행된다.
subscript(index: Int) -> Int {
get {
// Return an appropriate subscript value here.
}
set(newValue) {
// Perform a suitable setting action here.
}
}
setter에서 newValue의 타입은 반환 타입과 동일하다. 그리고 computed property처럼 파라미터를 지정하지 않는 경우 newValue라는 default value가 제공된다.
Read-only computed property처럼 get키워드와 set키워드를 제거하고 구현하지 않으면 자동으로 get으로 지정되어 특정 값을 반환하는 read-only subscript로 선언된다.
subscript(index: Int) -> Int {
// Return an appropriate subscript value here.
}
다음은 read-only subscript의 사용 예제다.
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// Prints "six times three is 18"
구조체의 인스턴스에서 multiplier(곱하는 값)은 3으로 지정되었다. threeTimesTable[6]의 호출처럼 subscript를 사용하여 threeTimesTable 인스턴스에 접근할 수 있다. 이는 테이블에서 6번째 값인 3*6=18을 반환한다.
Subscript는 특정 클래스나 구조체의 기능에 가장 적절한 방식으로 구현할 수 있다. 예를 들어 Swift의 Dictionary 타입은 딕셔너리의 인스턴스에 저장된 값을 검색하거나 설정하는 데 subscript를 사용한다. 다음은 딕셔너리에서 서브스크립트 사용 예제이다.
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2
numberOfLegs 딕셔너리는 [String:Int]타입으로 추론된다. 딕셔너리를 만든 후 서브스크립트를 이용하여 문자열 키의 값은 "bird" Int형 값은 2로 딕셔너리에 할당한다.
NOTE
Swift의 딕셔너리에서 subscript를 이용한 값의 반환 타입은 optinal type이다. 이유는 키로 검색하여 값이 있으면 값을 반환하는데 해당 키가 딕셔너리 안에 존재하지 않을 경우 nil을 반환하기 때문이다.
Subscrip는 입력 인자의 수, 타입 그리고 반환 타입과 상관이 없다. 함수와 마찬가지로 다양한 수의 파라미터를 가질 수 있고 파라미터가 default value(기본 값)을 가질 수 있다. 그러나 in-out-parameter는 가질 수가 없다.
그리고 subscript는 오버로딩을 허용한다. 그래서 인자형, 반환형을 달리하여 서브스크립트를 선언할 수 있다. 다음은 서브스크립트를 사용하어 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)
}
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
}
}
}
Matric는 rows 그리고 columns 2개의 파라미터를 가지는 initializer를 제공하고 이는 Double 타입으로 row * column 크기를 갖는다. 배열 요소들의 초깃값은 0.0이다. grid는 1차원 배열인데 이는 2차원 배열을 1차원으로 표현한다. (2차원을 1차원 가로로 쭉 펼쳤다고 생각하자.)
Matric 인스턴스에 적절한 row와 column을 넣어 초기화한다.
var matrix = Matrix(rows: 2, columns: 2)
위 예제는 2개의 rows와 2개의 columns을 가지는 새로운 Matrix 인스턴스를 생성한다. grid배열은 이 2차원 배열을 왼쪽에서 오른쪽으로 읽기 쉽게 펼쳤다. 그림을 보면 grid 배열이 2차원 배열을 왼쪽에서 오른쪽으로 쓰였다고 생각하면 된다.
matrix의 값은 행과 열을 콤마로 구분하고 서브스크립트에 전달한다.
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
그러면 matrix의 각 위치에 다음과 같이 값이 할당된다.
Matrix의 getter와 setter는 row와 column의 값이 유효한지 확인하는 assertions이 있다. 이는 Matrix 구조체의 indexIsValid 메소드에 의해서 수행된다.
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
다음과 같이 범위를 벗어난 subscript 접근은 assertion trigger가 일어난다.
let someValue = matrix[2, 2]
// This triggers an assert, because [2, 2] is outside of the matrix bounds.