/Swift

17. 서브스크립트

betterhee 2021. 1. 30. 17:19

클래스, 구조체, 열거형에는 컬렉션, 리스트, 시퀀스 등 타입의 요소에 접근하는 단축 문법인 서브스크립트(Subscript)를 정의할 수 있다.

// 함수 사용
someArray.element(at: index)
someDictionary.value(forKey: key)

// 서브스크립트 사용
someArray[index]
someDictionary[key]

17.1 서브스크립트 문법

서브스크립트 정의 문법

subscript(index: Int) -> Int {
    get {
        // 적절한 서브스크립트 결괏값 반환
    }

    set(newValue) { // 매개변수를 따로 명시해주지 않으면 설정자의 암시적 전달인지 newValue 사용 가능
        // 적절한 설정자 역할 수행
    }
}

읽기 전용 서브스크립트 정의 문법

subscript(index: Int) -> Int {
    get {
        // 적절한 서브스크립트 결괏값 반환
    }
}

subscript(index: Int) -> Int {
    // 적절한 서브스크립트 결괏값 반환
}

17.2 서브스크립트 구현

서브스크립트는 자신이 가지는 컬렉션, 리스트, 시퀀스 등의 요소를 반환하고 설정할 때 주로 사용합니다.

함수와 마찬가지로 서브스크립트는 여러 개의 매개변수를 가질 수 있고, 매개변수 기본값을 가질 수 있습니다.

그렇지만 입출력 매개변수(in-out parameters)[각주:1]는 가질 수 없습니다.

struct Student {
    var name: String
    var number: Int
}

class School {

    var number: Int = 0
    var students: [Student] = [Student]()

    func addStudent(name: String) {
        let student: Student = Student(name: name, number: self.number)
        self.students.append(student)
        self.number += 1
    }

    func addStudents(names: String...) {
        for name in names {
            self.addStudent(name: name)
        }
    }

    subscript(index: Int = 0) -> Student? {
        if index < self.number {
            return self.students[index]
        }
        return nil
    }
}

let highSchool: School = School()
highSchool.addStudents(names: "MiJeong","JuHyun", "JiYoung", "SeongUk", "MoonDuk")

print(highSchool[1]?.name) // Optional("JuHyun")
print(highSchool[]?.name)  // Optional("MiJeong")

17.3 복수 서브스크립트

클래스와 구조체는 필요한 만큼 얼마든지 서브스크립트를 구현할 수 있습니다.

서브스크립트를 여러 개 구현해도 외부에서 서브스크립트를 사용할 때 전달한 값의 타입을 유추하여 적절한 서브스크립트를 선택하여 실행합니다.

이렇게 여러 서브스크립트를 한 타입에 구현하는 것을 서브스크립트 중복 정의(Subscript Overloading)라고 합니다.

struct Student {
    var name: String
    var number: Int
}

class School {

    var number: Int = 0
    var students: [Student] = [Student]()

    func addStudent(name: String) {
        let student: Student = Student(name: name, number: self.number)
        self.students.append(student)
        self.number += 1
    }

    func addStudents(names: String...) {
        for name in names {
            self.addStudent(name: name)
        }
    }

    // 1.
    subscript(index: Int) -> Student? {
        get {
            if index < self.number {
                return self.students[index]
            }
            return nil
        }

        set {
            guard var newStudent: Student = newValue else {
                return
            }

            var number: Int = index

            if index > self.number {
                number = self.number
                self.number += 1
            }

            newStudent.number = number
            self.students[number] = newStudent
        }
    }

    // 2.
    subscript(name: String) -> Int? {
        get {
            return self.students.filter{ $0.name == name }.first?.number
        }

        set {
            guard var number: Int = newValue else {
                return
            }

            if number > self.number {
                number = self.number
                self.number += 1
            }


            let newStudent: Student = Student(name: name, number: number)
            self.students[number] = newStudent
        }
    }

    // 3.
    subscript(name: String, number: Int) -> Student? {
        return self.students.filter{ $0.name == name && $0.number == number }.first
    }
}

let highSchool: School = School()
highSchool.addStudents(names: "MiJeong","JuHyun", "JiYoung", "SeongUk", "MoonDuk")

// 1.
// subscript(index: Int) -> Student?
print(highSchool[1]) // Optional(Student(name: "JuHyun", number: 1))

// 2.
// subscript(name: String) -> Int?
print(highSchool["MiJeong"]) // Optional(0)
print(highSchool["DongJin"]) // nil

highSchool[0] = Student(name: "HongEui", number: 0)
highSchool["MangGu"] = 1

print(highSchool["JuHyun"])      // nil
print(highSchool["MangGu"])      // Optional(1)

// 3.
// subscript(name: String, number: Int) -> Student?
print(highSchool["SeongUk", 3])  // Optional(Student(name: "SeongUk", number: 3))
print(highSchool["HeeJin", 3])   // nil

이처럼 서브스크립트는 메서드인듯 아닌듯, 연산 프로퍼티인 듯 아닌 듯 중간 형태를 띄며 인스턴스 이름 뒤에 대괄호만 써서 편리하게 내부 값에 접근하고 설정해줄 수 있습니다.

17.4 타입 서브스크립트

타입 서브스크립트는 인스턴스가 아니라 타입 자체에서 사용할 수 있는 서브스크립트입니다.

타입 서브스크립트를 구현하려면 정의할 때 static 키워드를 붙여주면 됩니다. 클래스의 경우에는 class 키워드를 사용할 수도 있습니다.[각주:2]

enum School: Int {
    case elementary = 1, middle, high, university

    static subscript(level: Int) -> School? {
        return Self(rawValue: level)
        // return School(rawValue: level)와 동일
    }
}

let school: School? = School[2]
print(school)    // School.middle

야곰, 스위프트 프로그래밍

  1. 값이 아닌 참조를 전달하려면 입출력 매개변수를 사용 (C언어의 포인터와 유사함) [본문으로]
  2. 클래스에서 static 키워드와 class 키워드의 차이:
    static 으로 정의하면 상속 후 메서드 재정의가 불가능
    class 으로 정의하면 상속 후 메서드 재정의가 가능 [본문으로]