[Swift 공식문서 읽기]Inheritance

llim🧚🏻‍♀️·2021년 8월 18일
1

Swift

목록 보기
13/26
post-thumbnail

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서를 정독하기 시리즈입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

좀 더 편하게 보기위해 한국어로 번역된 사이트를 함께 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


상속

클래스는 메소드, 프로퍼티와 다른 특징(characteristics)을 다른 클래스로 부터 상속할 수 있습니다. 이것이 Swift에서 클래스가 다른 타입과 구분되는 근본적인 요소입니다. 클래스에서는 저장된 프로퍼티와 계산된 프로퍼티와 상관없이 상속받은 프로퍼티에 프로퍼티 옵저버를 설정해서 값 설정에 반응할 수 있습니다.

기반 클래스

다른 어떤 클래스로부터도 상속받지 않은 클래스를 기반 클래스라 합니다.
Swift에서는 SuperClass 지정없이 클래스 선언이 가능하고 그 클래스가 SuperClass가 됩니다.

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

let someVehicle = Vehicle()

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

서브클래싱

상속, 다시말해 서브클래싱을 하면 부모로 부터 성격을 상속받고 자기 자신 고유의 특성도 추가할 수 있습니다.

class Bicycle: Vehicle {
    var hasBasket = false
}

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

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

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

서브클래스로 생성된 클래스를 다시 서브클래싱 하는 것도 가능합니다. 이 때에도 자신의 부모 클래스의 속성은 물론
조부모(?)의 속성도 모두 사용 가능합니다.

오버라이딩

서브클래스에서는 부모클래에스에서 상속받은 것을 재정의 할 수 있습니다. 이것을 오버라이딩이라 부릅니다. 오버라이딩은 인스턴스 메소드, 인스턴스 프로퍼티, 타입 프로퍼티, 서브스크립트 모두 가능합니다.
오버라이딩을 위해서는 다른 선언 앞에 override 키워드를 붙여주고, Swift에서는 이 override키워드를 보면 부모에 그 정의가 있는지 확인합니다.
super키워드와 점문법 혹은 인덱스 구문으로 부모 클래스의 메소드, 프로퍼티, 서브스크립트에 접근할 수 있습니다. (super.someMethod, super.someProperty, super[someIndex])

메소드 오버라이드

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

let train = Train()
train.makeNoise()
// Prints "Choo Choo" : 새로 정의한 내용이 출력됩니다.

프로퍼티 오버라이드

서브클래스에서는 상속받은 저장된 프로퍼티, 계산된 프로퍼티 모두 오버라이드 가능합니다. 오버라이드시에는 프로퍼티의 이름과 타입을 명시 해야합니다. 왜냐하면 서브클래스에서는 단순히 상속받은 특정 형의 프로퍼티가 있다는 정도만 알고 있기 때문입니다.
상속받은 읽기전용(read only) 프로퍼티도 getter/setter를 정의해서 읽고/쓰기가 가능한(read-write) 프로퍼티로 변경해서 제공 가능합니다.
하지만 반대의 읽고/쓰기가 가능한 프로퍼티를 읽기전용 프로퍼티로 선언하는 것은 할 수 없습니다.
만약 setter를 오버라이드 해서 제공한다면 반드시 getter도 제공해야 합니다.

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

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

💡 그런데 override var currentSpeed: Double = 3과 같은 방식으로는 동작하지 않았습니다.
관련해서는 스택 오버 플로우 1번 참고자료2번 참고자료에서 내용을 찾아봤는데요.
지금 위에서 하려고 했던 방식은 단순히 새 값을 부여하는 것이고, 속성을 변경하는 것과는 다른 행위라고 합니다. 연산 프로퍼티를 재정의 하는 것이 속성을 변경하는 행위라고 볼 수 있겠네요.
다시 말해, 속성 값만 변경할 수 있기 때문에 저장 프로퍼티를 저장 프로퍼티로 재정의하는 것은 의미가 없다는 것 입니다.

관련 에러

1. Cannot override with a stored property
2. Variable with getter/setter cannot have an initial value

프로퍼티 옵저버 오버라이드

부모클래스에 선언된 프로퍼티에 서브클래스에서 옵저버를 추가할 수 있습니다.
(상수 프로퍼티와 읽기전용 프로퍼티에는 옵저버를 붙일 수 없습니다. 이 프로퍼티는 정의 그대로 set을 할 수 없는 프로퍼티이기 때문입니다. 또 같은 프로퍼티에 옵저버를 추가하고 setter를 추가해 둘을 동시에 사용할 수 없습니다. 이미 setter를 설정했다면 옵저버를 붙인 것과 같은 동작을 하기 때문입니다.)

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

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

💡 그런데 정말 부모 클래스의 프로퍼티 옵저버를 서브클래스에서 재정의해 사용할 수 있는건가요? 실제로 코드를 돌려본 결과로는, 부모클래스의 프로퍼티 옵저버가 재정의 된다기 보다는...? 부모클래스의 프로퍼티 옵저버도 동작하고, 새로 만든 자식클래스의 옵저버도 동작해서 2개의 옵저버가 동시에 반응하는 것을 확인했습니다.
그래서 또 다시 스택 오버 플로우를 참고했고, 옵저버 자체를 오버라이드 한다기 보다는, 옵저버에 들어가는 함수를 오버라이드 한다던지 다른 방법을 사용해야 한다는 생각이 들었습니다.
혹시 이와 관련해서 프로퍼티 옵저버를 오버라이드 할 수 있다면, 댓글로 꼭 남겨주세요...!

오버라이드 방지

서브클래스에서 특정 메소드, 프로퍼티, 서브스크립트가 오버라이드 되는 것을 방지하려면 final키워드를 사용합니다. 다시말해, final로 선언되면 override되는 것을 막을 수 있습니다. (final func, final class func, final subscript)
만일 final로 선언된 메소드, 프로퍼티, 서브스크립트를 오버라이드 하려고 하면 컴파일 시간(compile-time)에 에러가 발생합니다. 클래스 전체를 final로 선언해서 클래스 안의 모든 메소드, 프로퍼티 등이 override가 되는 것을 막을 수 있습니다.
(final class)

💡 final 키워드와 성능?
관련해서는 제드님의 블로그에서 일부 확인하였고, Understanding Swift Performance 영상을 확인해야겠다는 생각이 들었습니다.
결론만 인용하자면 결과적으로 class는 기본적으로 dynamic dispatch를 합니다. 하지만 모든 class가 이 dynamic dispatch를 사용 할 필요는 없죠.
class를 서브클래싱하지 않으면 final로 명시하면 됩니다. 컴파일러는 이를 보고 static dispatch를 하게 될것입니다.
또한, 컴파일러가 앱에서 class를 서브클래싱 하지 않을 것이라는것을 추론하고 입증 할 수 있다면, 기회를 보고 dynamic dispatch를 대신하여 static dispatch를 하게 됩니다. (관련해서 OptimizationTips 페이지에서 private랑 fileprivate명시해주는 것도 나온다고 합니다.)

추가로 라이노님의 블로그 글과 같은 것들을 찾아보며
static dispatch와 dynamic dispatch에 대해 학습하면 좋을 것 같습니다!


오늘도 스위프트 공식문서를 정리해보았군욥~
뭔가 봐야할 영상과 사이트가 늘어난 것 같네요..^^...
다음편도 힘내보겠습니다!

감사합니다🙇🏻‍♀️

profile
한달 차 iOS 개발자입니다🐥

0개의 댓글