Subscript

Eddy📱·2022년 8월 12일
0

Swift

목록 보기
1/11
post-thumbnail

subscript에서 인덱스를 활용해서 설정이나 얻는것과 관련된 메서드가 필요하지 않고도 값들을 설정하거나 얻을 수 있다.

보통 Array나 딕셔너리에서 값을 얻으려면 아래와 같이 써야한다.

someArray[index]
someDictionary[key]

하나의 타입을 통해 여러개의 subscripts을 정의할 수 있다.

그리고 여러 개의 input parameters와 함께 subscripts를 정의해서 원하는 타입의 필요에 맞게 만들어줄 수 있다.

이제 정의를 보았으니 어떻게 사용하는지 보도록 하자.

공식문서에 따르면 아래와 같이 사용할 수 있다.

subscript(index: Int) -> Int {
    get {
        // subscript 값을 리턴한다.
    }
    set(newValue) {
        // 설정할 수 있다.
    }
}

사용하려면 하나 이상의 parameter와 리턴 타입을 명시해주어야 한다.

보기에 인스턴스 메서드와 연산프로퍼티랑 비슷한 성격을 볼 수 있다.

인스턴스 메스더와 차이점으로는 여기에서는 read-write, read-only만 가능하다.

이 스타일은 연산프로퍼티와 동일하다!

만약 오로지 읽기전용으로만 사용하려면 아래처럼 쓸 수 있다.

subscript(index: Int) -> Int {
    // subscript 값을 리턴한다.
}

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"

TimesTable이라는 구조체에서 multiplier라는 곱하기 상수가 있다.

이제 subscript에서는 index라는 파라미터로 값을 받아오고 이를 multiplier를 구조체 초기화때 값을 받기 때문에 이 2개를 활용해서 곱한 값을 내보낸다.

그렇게 하면 프린트에서 3 * 6 = 18 이 나오는 것을 확인할 수 있다.

subscript라는 용어는 사용처에 따라 의미가 달라질 수 있다.

대부분 사용하는 방법은 collection, list, sequence에서 각 하나의 요소들을 접근할 때 사용한다.

그리고 또한 특정 class, struct의 함수에서도 사용할 수 있다.

예를 들어 스위프트에서 Dictionary 타입에서 subscript를 활용해서 값을 설정하거나 구현해준다.

이는 우리가 자주 사용하는 딕셔너리 스타일인데 이게 subscript로 동작하는 것이다!

var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

이게 각각의 딕셔너리의 값 타입을 subscript에 할당해주는 것이고 또한 값을 할당해주는 부분에서도 subscript의 기능이 활용된다.

그런데 우리가 위에서 subscript를 사용할 때 옵셔널이 없었다.

하지만 dictionary에서 값을 얻거나 반환할 때 옵셔널 타입을 사용한다.

이는 딕셔너리에서 optional subscript를 사용하고 있는 것이다!

왜냐하면 여기에서 항상 모든 키가 값을 가진다고 생각하지 않고 nil를 가질 수도 있다고 생각해서 그렇게 사용하고 있다.

Subscript Options

subscript는 input 파라미터를 가질 수 있고 리턴타입도 존재한다.

함수가 비슷해보이지만 subscript에서는 in-out 파라미터는 사용할 수 없는 차이점이 존재한다.

클래스나 구조체에서 많은 subscript 구현들을 제공하고 있다.

subscript가 사용되는 곳인 subscript 내부에서 값들이나 값의 타입을 활용해서 적절한 subscript를 사용할 수 있다.

이러한 것을 보통 multiple subscripts라고 하고 잘 알려진 말로는 subscript overloading이라고 한다.

보통 하나의 파라미터를 가지는게 일반적이다. 아래 예시를 보면 여러개를 사용하는 것을 볼 수 있다.

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
        }
    }
}

천천히 하나씩 보면 Matrix라는 구조체에 rows, columns 상수가 존재하고 grid라는 rows * columns가 있다.

이는 구조체에서 초기 설정에서 값을 넣어주며 사용하고 있다.

다음으로 이 index가 유효한지 체크해주는 indexIsValid 메서드가 존재해서 조건이 성립하면 Bool를 내보내고 있다.

그리고 subscript에서는 index가 범위 밖인것을 확인하도록 한다.

assert로 indexIsValid 메서드를 호출해서 bool로 값을 받고 있다.

그래서 true면 프린트문을 찍어주고 그게 아니라면 원하는 값을 grid를 통해 만들어주고 있다.

스위프트 언어의 String에서 subscript를 통해 문자에 접근할 수 없는 이유

String은 [Int]로 접근하지 못하고 String.index로 접근해야한다.

요게 무슨말이냐면 아래를 보면 이해할 수 있다.

let example = "Eddy"

이곳에서 우리는 E, d, d, y 를 따로따로 접근하고싶다.

하지만 swift에서는 [Int]로 접근하는 방법이 없다!

example[0] // error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.

이렇게 할수가 없는 것이다!!!

그래서 String.index를 활용해야한다.

example.startIndex  // E
example.endIndex // y

이와 같은 String.index를 통해 하나씩 접근해야한다!

왜 swift에서는 지원하지 않을까?

그 이유는 다음과 같다.

  • 하나 이상의 Unicode Scalar가 모여 Character를 이루고 Character가 모여 String을 이룬다
  • Character은 1개 이상의 unicode scalar로 이루어져있어 크기가 가변적이다.
  • 어떠한 유니코드 스칼라를 기준으로 인덱싱을 지원해야하는 지가 애매하기 때문에 subscript로 접근하는 걸 제공하지 않는다.

Unicode Scalar란?

Swift의 문자열 타입은 Unicode Scalar 값에서 만들어진다.

여기에서 참고해볼 수 있다.

여기서 말하는 Unicode Scalar 의미는 아래와 같다.

  • 크기가 가변적인 String 문자열을 하나하나 개별적으로 접근하기 위한 방법
  • Unicode기반 21-bit 코드
  • UTF-32랑 거의 동일
  • 하나 이상의 Unicode Scalar가 모여 Character를 이룸

unicode Scalar의 가변성에 대한 코드 예시

let emojiWomanFamily = "👩‍👩‍👧‍👧"  // 두명의 엄마와 두명의 딸로 구성된 가족
print(emojiWomanFamily.unicodeScalars.count)  // 7

let emojiWomanFamily = "👩‍👩‍👧‍👧"  // 두명의 엄마와 두명의 딸로 구성된 가족
print(emojiWomanFamily.count)    // 1

이게 7로 나오는 이유는 각각 이모지 하나마다 숫자가 매겨지고 그외에 사이 공백도 매겨지며 7이 나오고 있다!

이처럼 가변적인 String 문자열을 하나씩 개별적으로 접근하고 있는 것을 볼 수 있다.

Collection에 접근하는 방식에 관한 프로토콜

BidirectionalCollection - 후방, 전방 순회를 둘 다 지원하는 컬렉션 Protocol, 시간복잡도 O(N) 접근

RandomAccessCollection - 바로 접근 가능한 컬렉션 Protocol / 시간복잡도 O(1) 접근

Unicode Scalar로 접근하는 특성상 직접 접근이 하닌 순차적인 접근으로 가야한다.

한 Character가 Unicode Scalar를 몇개 가지고 있을지 알 수 없기 때문에 순차적으로 접근하면서 체크해야 하기 때문이다.

또한 String은 BidirectionalCollection을 따르고 있기 때문에 직접 접근이 제한된다.

스위프트 언어는 왜 Character가 unicode scalar로 이뤄졌을까?

유니코드 문자들이 깨지지 않은 상태로 보여줄 수 있기 때문이다

하지만 character 단위 문자열 처리에 비용이 많이 든다는 단점이 있다

참고사이트

https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html
https://velog.io/@haze5959/Swift-Unicode-Scalar-그리고-문자열-count-시간-복잡도-관계
https://medium.com/@esung/swift의-문자열과-유니코드-af37a5d503a4

profile
Make a better world

0개의 댓글