상속

상속 (Inheritance)

클래스는 다른 클래스로부터 메서드, 프로퍼티, 그리고 여러 특징을 상속 받을 수 있어요. 어떤 클래스가 다른 클래스로부터 상속을 받을 때, 상속받는 클래스를 서브클래스(subclass), 상속의 원천이 되는 클래스를 *슈퍼클래스(superclass)*라고 불러요. 상속은 클래스를 Swift의 다른 타입들과 구분 짓는 가장 기본적인 동작이기도 합니다.

Swift의 클래스는 슈퍼클래스에 속한 메서드, 프로퍼티, 서브스크립트를 호출하고 접근할 수 있어요. 그리고 그런 메서드·프로퍼티·서브스크립트의 동작을 다듬거나 바꾸기 위해 자신만의 오버라이딩 버전을 제공할 수도 있습니다. Swift는 오버라이드 정의가 슈퍼클래스의 정의와 일치하는지 검사해서, 여러분의 오버라이드가 올바른지 확인해 줘요.

클래스는 상속받은 프로퍼티에 프로퍼티 옵저버를 추가해서, 프로퍼티의 값이 바뀔 때 알림을 받을 수도 있습니다. 프로퍼티 옵저버는 원래 저장 프로퍼티이든 연산 프로퍼티이든 상관없이 어떤 프로퍼티에도 추가할 수 있어요.

출처: The Swift Programming Language

본문

기본 클래스 정의하기 (Defining a Base Class)

다른 클래스로부터 상속받지 않는 클래스를 *기본 클래스(base class)*라고 해요.

Note: Swift의 클래스는 보편적인(universal) 기본 클래스로부터 상속받지 않습니다. 슈퍼클래스를 지정하지 않고 정의하는 클래스는 여러분이 그 위에 쌓아 올릴 수 있는 기본 클래스가 자동으로 됩니다.

아래 예시는 Vehicle이라는 기본 클래스를 정의해요. 이 기본 클래스는 currentSpeed라는 저장 프로퍼티를 정의하는데, 기본값은 0.0이에요(그래서 프로퍼티 타입은 Double로 추론됩니다). currentSpeed 프로퍼티의 값은 description이라는 읽기 전용 연산 String 프로퍼티가 사용해서 차량에 대한 설명을 만듭니다.

Vehicle 기본 클래스는 makeNoise라는 메서드도 정의해요. 이 메서드는 기본 Vehicle 인스턴스에서는 실제로 아무것도 하지 않지만, 나중에 Vehicle의 서브클래스들이 이 메서드를 커스터마이즈할 거예요:

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // do nothing - an arbitrary vehicle doesn't necessarily make a noise
    }
}

Vehicle 인스턴스는 *이니셜라이저 구문(initializer syntax)*으로 만드는데, 타입 이름 뒤에 빈 괄호를 붙여서 써요:

let someVehicle = Vehicle()

Vehicle 인스턴스를 만들었다면, description 프로퍼티에 접근해서 차량의 현재 속도를 사람이 읽기 좋은 설명으로 출력할 수 있어요:

print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour

Vehicle 클래스는 임의의 차량에 공통인 특징을 정의하지만, 그 자체로는 쓸모가 많지 않아요. 더 유용하게 만들려면 더 구체적인 종류의 차량을 설명하도록 다듬어야 합니다.

서브클래싱 (Subclassing)

*서브클래싱(subclassing)*은 기존 클래스 위에 새 클래스를 만드는 행위예요. 서브클래스는 기존 클래스의 특징을 상속받으며, 이후 그 특징을 다듬을 수 있어요. 서브클래스에 새 특징을 추가할 수도 있습니다.

서브클래스가 슈퍼클래스를 가진다는 것을 나타내려면, 서브클래스 이름을 슈퍼클래스 이름 앞에 쓰고 콜론으로 구분해요:

class SomeSubclass: SomeSuperclass {
    // subclass definition goes here
}

다음 예시는 Vehicle을 슈퍼클래스로 가지는 Bicycle이라는 서브클래스를 정의해요:

class Bicycle: Vehicle {
    var hasBasket = false
}

Bicycle 클래스는 currentSpeed, description 프로퍼티와 makeNoise() 메서드 같은 Vehicle의 모든 특징을 자동으로 얻게 됩니다.

상속받은 특징에 더해, Bicycle 클래스는 hasBasket이라는 새 저장 프로퍼티를 정의하는데 기본값은 false예요(그래서 프로퍼티 타입은 Bool로 추론됩니다).

기본적으로 여러분이 만드는 새 Bicycle 인스턴스에는 바구니가 없어요. 특정 Bicycle 인스턴스의 hasBasket 프로퍼티는 그 인스턴스를 만든 뒤 true로 설정할 수 있습니다:

let bicycle = Bicycle()
bicycle.hasBasket = true

Bicycle 인스턴스가 상속받은 currentSpeed 프로퍼티를 수정하고, 상속받은 description 프로퍼티를 조회할 수도 있어요:

bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour

서브클래스는 그 자신도 서브클래싱될 수 있어요. 다음 예시는 "tandem"이라고 알려진 2인승 자전거를 위해 Bicycle의 서브클래스를 만듭니다:

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

TandemBicycle의 모든 프로퍼티와 메서드를 상속받고, Bicycle은 다시 Vehicle의 모든 프로퍼티와 메서드를 상속받아요. Tandem 서브클래스는 기본값 0을 가진 currentNumberOfPassengers라는 새 저장 프로퍼티도 추가합니다.

Tandem 인스턴스를 만들면, 새 프로퍼티와 상속받은 프로퍼티를 모두 다룰 수 있고, Vehicle로부터 상속받은 읽기 전용 description 프로퍼티도 조회할 수 있어요:

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour

오버라이딩 (Overriding)

서브클래스는 슈퍼클래스로부터 상속받게 될 인스턴스 메서드, 타입 메서드, 인스턴스 프로퍼티, 타입 프로퍼티, 또는 서브스크립트에 대해 자신만의 커스텀 구현을 제공할 수 있어요. 이걸 *오버라이딩(overriding)*이라고 합니다.

그렇지 않으면 상속받게 될 특징을 오버라이드하려면, 오버라이딩 정의 앞에 override 키워드를 붙여요. 이렇게 해서 여러분이 의도적으로 오버라이드를 제공한다는 것과, 실수로 일치하는 정의를 만들지 않았다는 것을 분명히 하는 거죠. 실수로 오버라이드하면 예상치 못한 동작이 생길 수 있는데, override 키워드 없이 오버라이드된 것은 코드가 컴파일될 때 오류로 진단됩니다.

override 키워드는 Swift 컴파일러가 오버라이딩 클래스의 슈퍼클래스(또는 그 조상 중 하나)에 오버라이드와 일치하는 선언이 있는지 검사하도록 만들기도 해요. 이 검사 덕분에 여러분의 오버라이딩 정의가 올바른지 확인할 수 있습니다.

슈퍼클래스 메서드·프로퍼티·서브스크립트에 접근하기

서브클래스에서 메서드, 프로퍼티, 서브스크립트 오버라이드를 제공할 때, 오버라이드의 일부로 기존 슈퍼클래스 구현을 사용하는 게 유용할 때가 있어요. 예를 들어 기존 구현의 동작을 다듬거나, 기존 상속 변수에 수정된 값을 저장할 수 있죠.

이런 경우에 적합한 곳에서는 super 접두사를 사용해서 메서드·프로퍼티·서브스크립트의 슈퍼클래스 버전에 접근해요:

  • someMethod()라는 오버라이드된 메서드는 오버라이딩 메서드 구현 안에서 super.someMethod()를 호출해서 someMethod()의 슈퍼클래스 버전을 호출할 수 있어요.
  • someProperty라는 오버라이드된 프로퍼티는 오버라이딩 getter 또는 setter 구현 안에서 super.somePropertysomeProperty의 슈퍼클래스 버전에 접근할 수 있어요.
  • someIndex에 대한 오버라이드된 서브스크립트는 오버라이딩 서브스크립트 구현 안에서 super[someIndex]로 같은 서브스크립트의 슈퍼클래스 버전에 접근할 수 있어요.

메서드 오버라이딩

상속받은 인스턴스 또는 타입 메서드를 오버라이드해서, 서브클래스 안에서 그 메서드에 맞춤형 또는 대안 구현을 제공할 수 있어요.

다음 예시는 Train이라는 Vehicle의 새 서브클래스를 정의하는데, TrainVehicle로부터 상속받은 makeNoise() 메서드를 오버라이드합니다:

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

Train 인스턴스를 만들고 그 makeNoise() 메서드를 호출하면, Train 서브클래스 버전의 메서드가 호출되는 것을 볼 수 있어요:

let train = Train()
train.makeNoise()
// Prints "Choo Choo".

프로퍼티 오버라이딩

상속받은 인스턴스 또는 타입 프로퍼티를 오버라이드해서, 그 프로퍼티에 대한 자신만의 커스텀 getter와 setter를 제공하거나, 프로퍼티 옵저버를 추가해 오버라이딩 프로퍼티가 바탕이 되는 프로퍼티 값의 변화를 관찰하게 할 수 있어요.

프로퍼티 getter와 setter 오버라이딩

어떤 상속받은 프로퍼티든 커스텀 getter(그리고 적절하다면 setter)를 제공해서 오버라이드할 수 있어요. 상속받은 프로퍼티가 원래 저장 프로퍼티로 구현되었는지 연산 프로퍼티로 구현되었는지는 상관없습니다. 상속받은 프로퍼티의 저장/연산 여부는 서브클래스가 알지 못해요. 서브클래스는 상속받은 프로퍼티가 특정 이름과 타입을 가진다는 것만 알 수 있어요. 항상 오버라이드하는 프로퍼티의 이름과 타입을 둘 다 명시해야 하는데, 그래야 컴파일러가 오버라이드가 같은 이름과 타입의 슈퍼클래스 프로퍼티와 일치하는지 검사할 수 있어요.

서브클래스 프로퍼티 오버라이드에서 getter와 setter를 둘 다 제공하면, 상속받은 읽기 전용 프로퍼티를 읽기-쓰기 프로퍼티로 만들 수 있어요. 하지만 상속받은 읽기-쓰기 프로퍼티를 읽기 전용 프로퍼티로 만들 수는 없습니다.

Note: 프로퍼티 오버라이드의 일부로 setter를 제공한다면, 그 오버라이드에 getter도 반드시 제공해야 해요. 오버라이딩 getter 안에서 상속받은 프로퍼티의 값을 수정하고 싶지 않다면, getter에서 super.someProperty를 반환해서 상속받은 값을 그대로 전달하면 돼요. 여기서 someProperty는 오버라이드하는 프로퍼티의 이름이에요.

다음 예시는 Vehicle의 서브클래스인 Car라는 새 클래스를 정의해요. Car 클래스는 기본 정수값 1을 가진 gear라는 새 저장 프로퍼티를 소개합니다. Car 클래스는 Vehicle로부터 상속받은 description 프로퍼티도 오버라이드해서, 현재 기어를 포함한 커스텀 설명을 제공해요:

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

description 프로퍼티의 오버라이드는 super.description을 호출해서 시작하는데, Vehicle 클래스의 description 프로퍼티를 반환해요. 그리고 Car 클래스의 description 버전은 이 설명의 끝에 현재 기어에 대한 정보를 제공하는 추가 텍스트를 덧붙입니다.

Car 클래스 인스턴스를 만들고 gearcurrentSpeed 프로퍼티를 설정하면, description 프로퍼티가 Car 클래스 안에서 정의된 맞춤형 설명을 반환하는 것을 볼 수 있어요:

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3
프로퍼티 옵저버 오버라이딩

프로퍼티 오버라이딩을 사용해서 상속받은 프로퍼티에 프로퍼티 옵저버를 추가할 수 있어요. 이 덕분에 상속받은 프로퍼티가 원래 어떻게 구현되었는지와 상관없이, 값이 바뀔 때 알림을 받을 수 있습니다. 프로퍼티 옵저버에 대한 자세한 내용은 doc:Properties#Property-Observers를 참고하세요.

Note: 상속받은 상수 저장 프로퍼티 또는 상속받은 읽기 전용 연산 프로퍼티에는 프로퍼티 옵저버를 추가할 수 없어요. 이런 프로퍼티의 값은 설정될 수 없으므로, 오버라이드의 일부로 willSet 또는 didSet 구현을 제공하는 것은 적절하지 않습니다.

같은 프로퍼티에 오버라이딩 setter와 오버라이딩 프로퍼티 옵저버를 둘 다 제공할 수도 없다는 점도 참고하세요. 프로퍼티 값의 변화를 관찰하고 싶은데 이미 그 프로퍼티에 커스텀 setter를 제공하고 있다면, 커스텀 setter 안에서 값 변화를 관찰하면 됩니다.

다음 예시는 Car의 서브클래스인 AutomaticCar라는 새 클래스를 정의해요. AutomaticCar 클래스는 자동 기어박스를 가진 차를 나타내는데, 현재 속도에 기반해 적절한 기어를 자동으로 선택합니다:

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

AutomaticCar 인스턴스의 currentSpeed 프로퍼티를 설정할 때마다, 프로퍼티의 didSet 옵저버가 인스턴스의 gear 프로퍼티를 새 속도에 적절한 기어로 설정해요. 구체적으로 말하면, 프로퍼티 옵저버는 새 currentSpeed 값을 10으로 나누고 가장 가까운 정수로 내림한 뒤 1을 더한 기어를 선택합니다. 속도 35.0은 기어 4를 만들어내죠:

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4

오버라이딩 방지 (Preventing Overrides)

메서드·프로퍼티·서브스크립트를 final로 표시해서 오버라이드되는 것을 막을 수 있어요. 메서드·프로퍼티·서브스크립트의 소개 키워드 앞에 final 수식어를 쓰면 됩니다(예: final var, final func, final class func, final subscript).

서브클래스에서 final 메서드·프로퍼티·서브스크립트를 오버라이드하려는 어떤 시도든 컴파일 타임 오류로 보고됩니다. 확장(extension)에서 클래스에 추가하는 메서드·프로퍼티·서브스크립트도 그 확장의 정의 안에서 final로 표시할 수 있어요. 자세한 내용은 doc:Extensions를 참고하세요.

클래스 정의에서 class 키워드 앞에 final 수식어를 써서(final class) 클래스 전체를 final로 표시할 수도 있어요. final 클래스를 서브클래싱하려는 어떤 시도든 컴파일 타임 오류로 보고됩니다.

더 알아보기

  • 상속받은 프로퍼티에 프로퍼티 옵저버를 붙이는 방법과 주의사항은 속성(Properties) 챕터의 "프로퍼티 옵저버"를 참고하세요.
  • final 수식어로 클래스에 추가한 요소를 표시하는 내용은 확장(Extensions) 챕터를 참고하세요.
  • 서브클래스와 상속이 실제 코드에서 어떻게 어울리는지 더 보고 싶다면, 이 문서의 코드를 Swift 플레이그라운드에 순서대로 붙여 넣어 직접 실행해 보는 것도 좋아요.