소피it블로그

[Swift 공식문서] Inheritance 정리 본문

개발_iOS/스위프트

[Swift 공식문서] Inheritance 정리

sophie_l 2022. 6. 9. 19:59

https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html

 

Inheritance — The Swift Programming Language (Swift 5.7)

Inheritance A class can inherit methods, properties, and other characteristics from another class. When one class inherits from another, the inheriting class is known as a subclass, and the class it inherits from is known as its superclass. Inheritance is

docs.swift.org

클래스는 다른 클래스로부터 메서드, 프라퍼티와 다른 특징들을 상속받을 수 있다. 한 클래스가 다른 클래스에서 상속을 받을 때 상속을 받는 클래스를 하위 클래스, 상속을 해주는 클래스는 슈퍼 클래스라고 부른다. 상속은 클래스를 다른 타입으로부터 차별화해주는 근본적인 성질이다.

스위프트의 슈퍼 클래스에 속한 클래스는 메서드, 프라퍼티와 subscripts를 호출하거나 이에 접근할 수 있고, 해당 메서드, 프라퍼티, subscript를 오버라이딩 해서 수정해줄 수 있다. 스위프트는 오버라이딩 정의에 매칭되는 슈퍼클래스의 정의가 있는 것을 확인해줌으로써 오버라이딩이 정확한지 알 수 있게 돕는다.

클래스는 프라퍼티의 값이 변화할 때 이를 알 수 있게 상속된 프라퍼티에 프라퍼티 옵저버를 추가할 수 있다. 프라퍼티 옵저버는 저장된 프라퍼티인지 계산된 프라퍼티인지와는 상관 없이 모든 프라퍼티에 더해질 수 있다.

 

1. Base Class 정의하기

 

다른 클래스로부터 상속받지 않는 모든 클래스는 base class라고 한다.

아래의 예시는 Vehicle이라고 하는 베이스 클래스를 정의한다. 이 베이스 클래스는 디폴트 값으로 0.0(Double로 추론되는 값)을 갖는 currentSpeed라는 저장된 프라퍼티를 정의한다. currentSpeed 프라퍼티의 값은 탈것에 대한 묘사를 생성해내기 위해 읽기 전용 계산된 String 프라퍼티인 description에 의해 사용된다.

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의 새 인스턴스를 생성해줄 수 있는데, 이는 타입명과 빈 괄호를 연달아 써줌으로써 가능하다.

let someVehicle = Vehicle()

새로운 Vehicle 인스턴스를 생성해준 후에는 description 프라퍼티에 접근해서 해당 탈것의 현재 속도에 대한 인간이 읽을 수 있는 묘사를 출력할 수 있다.

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

Vehicle 클래스는 임의의 탈것에 대해 공통된 특징을 정의해주지만, 그 자체로는 크게 쓸모가 없다. 이를 더 쓸모있게 만들기 위해서는 여러가지 다양한 탈것에 대해 묘사할 수 있도록 정제해야 한다.

 

2. 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 인스턴스는 basket을 갖지 않는 게 디폴트가 된다. 특정 Bicycle 인스턴스를 생성한 후 해당 인스턴스에 대해hasBasket  프라퍼티를 true로 설정해줄 수 있다.

let bicycle = Bicycle()
bicycle.hasBasket = true

Bicycle 인스턴스의 상속받은 currentSpeed 프라퍼티 또한 수정해줄 수 있다.

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

서브클래스 그 자체도 서브클래싱될 수 있다. 다음 예시에서는 좌석이 두 개인 자전거를 위해 tandem이라는 Bicycle의 서브클래스를 생성한다.

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

Tandem은 Bicycle로부터 모든 프라퍼티와 메서드를 상속받는데, 이는 결국에는 Vehicle의 모든 프라퍼티와 메서드를 상속받는 것이다. Tandem 서브클래스는 또한 currenNumberOfPassengers라고 하는 디폴트 값을 0인 새 저장된 프라퍼티를 정의한다.

Tandem의 인스턴스를 생성할 경우 새로운 프라퍼티나 상속된 프라퍼티 모두를 사용하여 작업할 수 있다.

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

 

3. 오버라이딩

 

서브클래스는 슈퍼클래스로부터 상속받을 수 있는 인스턴스 메서드, 타입 메서드, 인스턴스 프라퍼티, 타입 프라퍼티, 혹은 서브스크립트에 대해서 자신만의 커스텀 구현을 제공할 수 있다. 이를 오버라이딩이라고 한다.

상속받은 부분을 오버라이딩 하기 위해서는 override 키워드를 사용해서 오버라이딩을 정의한다. 그렇게 함으로써 오버라이딩을 하는 것임을 확실히 해줄 수 있다. 실수로 오버라이딩을 하면 예상치 못한 일들이 발생할 수 있고 override 키워드 없는 오버라이딩은 컴파일시 에러로 판단된다.

override 키워드는 또한 스위프트 컴파일러로 하여금 오버라이딩하는 클래스의 슈퍼클래스에 오버라이딩에 매칭되는 선언이 있음을 확인하게 해준다. 이를 통해 오버라이딩 정의가 옳다는 것을 확신할 수 있다.

 

(1) 슈퍼클래스의 메서드, 프라퍼티, 서브스크립트에 접근하기

 

서브클래스에 메서드, 프라퍼티, 혹은 서브스크립트의 오버라이딩을 제공할 경우 이미 존재하는 슈퍼클래스의 구현을 오버라이딩의 일부로 사용해주는 것이 도움이 된다. 예를 들어 이미 존재하는 구현의 특성을 손보거나 이미 존재하는 상속받은 변수에 수정된 값을 저장해줄 수 있다.

적절한 경우에 슈퍼클래스의 메서드, 프라퍼티, 서브스크립트에 super 접두어를 통해 접근할 수 있다.

 

  • someMethod()라는 이름의 오버라이딩된 메서드는 오버라이딩 메서드 구현부에서 someMethod()의 슈퍼클래스 버전을 super.someMethod()를 통해 호출할 수 있다.
  • 오버라이딩된 프라퍼티 someProperty는 오버라이딩 게터나 세터 구현부에서 슈퍼클래스 버전의 someProperty에 super.someProperty로 접근할 수 있다.
  • someIndex의 오버라이딩된 서브스크립트는 오버라이딩 서브스크립트 구현부 안에서 똑같은 서브스크립트의 슈퍼클래스 버전에 super[someIndex]를 통해 접근해줄 수 있다.

(2) 메서드 오버라이딩하기

 

서브클래스 내에서 메서드의 수정된 구현을 제공하기 위해 상속받은 인스턴스나 타입 메서드를 오버라이딩해줄 수 있다.

이하의 예시에서는 Vehicle의 새로운 서브클래스인 Train을 정의하는데, 이는 Vehicle에서 상속받는 makeNoise() 메서드를 오버라이드한다.

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

Train의 새로운 인스턴스를 생성한 후 makeNoise() 메서드를 호출할 경우 Train 서브클래스 버전의 메서드가 호출됨을 볼 수 있다.

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

(3) 프라퍼티 오버라이딩하기

 

자신만의 커스텀 게터와 세터를 프라퍼티에 제공해주거나, 오버라이딩 프라퍼티가 프라퍼티 값의 변화를 관측할 수 있도록 하는 프라퍼티 옵저버를 추가해주기 위해 상속받은 인스턴스나 타입 프라퍼티를 오버라이딩해줄 수 있다.

 

i) 프라퍼티 게터와 세터 오버라이딩하기

 

상속된 프라퍼티가 저장된 프라퍼티로 구현되었는지 계산된 프라퍼티로 구현되었는지 여부와 관련 없이, 상속된 프라퍼티를 오버라이딩해주기 위해 커스텀 게터와 세터를 제공해줄 수 있다. 상속된 프라퍼티의 저장 여부 혹은 계산 여부는 서브클래스에서는 알려지지 않는다. 서브클래스는 상속된 프라퍼티가 특정 이름과 타입이 있다는 것만 알 수 있다. 컴파일러가 오버라이딩된 프라퍼티가 슈퍼클래스 프라퍼티와 같은 이름과 타입으로 매칭된다는 것을 확인할 수 있도록 프라퍼티를 오버라이딩할 때는 그 이름과 타입을 명시해줘야 한다.

상속된 읽기 전용 프라퍼티를 읽고 쓰기가 가능한 프라퍼티로 제공해줄 수도 있다. 이 경우 서브클래스 프라퍼티의 오버라이딩에 게터와 세터를 둘 다 제공한다. 그러나 읽고 쓰기가 가능한 프라퍼티를 상속한 후 읽기 전용으로 제공할 수는 없다.

이하의 예시는 Car라고 불리는 새로운 클래스를 정의하는데, 이는 Vehicle의 서브클래스이다. Car 클래스는 gear라고 불리는 디폴트 정수값 1을 갖는 새로운 저장된 변수를 도입한다. Car 클래스는 또한 현재의 기어를 포함하는 커스텀 description을 제공하기 위해 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은 현재의 기어에 관한 정보를 제공하기 위해 description의 마지막에 몇가지 텍스트를 추가한다.

만약 Car 클래스의 인스턴스를 생성하고 gear와 currentSpeed 프라퍼티를 설정하면, description 프라퍼티가 Car 클래스안에서 정의되어 수정된 description을 리턴한다는 것을 볼 수 있다.

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

ii) 프라퍼티 옵저버 오버라이딩하기

 

프라퍼티 옵저버를 상속된 프라퍼티에 추가해주기 위해 프라퍼티 오버라이딩을 사용해줄 수 있다. 이를 통해 프라퍼티가 어떻게 처음 구현되었는지와는 관련 없이 상속된 프라퍼티의 값이 변할 때면 공지를 받을 수 있다.

이하의 예시는 AutomaticCar라는 새로운 클래스를 정의하는데, 이는 Car의 서브클래스이다. 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

 

4. 오버라이딩 방지하기

 

메서드, 프라퍼티, 또는 서브스크립트가 오버라이딩되는 것을 final로 마킹함으로써 방지할 수 있다. final 수식어를 메서드, 프라퍼티, 서브스크립트의 introducer 키워드 앞에 적어줌으로써 할 수 있다(final var, final func, final class func, final subscript 등).

서브클래스에서 final 메서드나 프라퍼티, 서브스크립트를 오버라이딩하려는 시도는 컴파일 타임 에러로 이어진다. extension을 통해 클래스에 더하는 메서드, 프라퍼티, 서브스크립트 익스텐션의 정의에서 final로 마킹될 수도 있다.

final 수식어를 클래스 정의시 class 키워드 앞에 위치시킴으로써 클래스 전체를 final로 마킹해줄 수도 있다(final class). 파이널 클래스를 서브클래싱하려는 시도는 역시 컴파일 타임 에러로 이어진다.