상속
- 다른 클래스로부터 상속을 받지 않은 클래스를
기반클래스
(Base Class)라고 한다.
- 상속은 기반클래스를 다른 클래스에서 물려받는 것을 말한다.
- 클래스는
메서드
나 프로퍼티
등을 다른 클래스로부터 상속 가능하다.
- 상속은 스위프트의 다른 타입과 클래스를 구별 짓는 클래스만의 특징이다.
- 스위프트의 클래스는 부모클래스로부터 물려받은 메서드를 호출할 수 있고 프로퍼티에 접근할 수 있으며 서브스크립트도 사용할 수 있다.
- 자식클래스는 부모클래스의 프로퍼티, 메서드, 서브스크립트 등을 자신만의 내용으로
재정의
할 수도 있다.
- 이때 재정의한다는 것을 명확히 확인해주어야 한다.
- 상속받은 프로퍼티에 프로퍼티의 값이 변경되었을 때 알려주는
프로퍼티 감시자도 구현
할 수 있다.
- 연산 프로퍼티를 정의해준 클래스에서는 연산 프로퍼티에 프로퍼티 감시자를 구현 할 수 없지만, 부모클래스에서 연산 프로퍼티로 정의한 프로퍼티든 저장 프로퍼티로 정의한 프로퍼티든 자식클래스에서는 프로퍼티 감시자를 구현할 수 있다.
재정의
- 자식클래스는 부모클래스로부터 물려받은 특성을 그대로 사용하지 않고 자신만의 기능으로 변경하여 사용할 수 있는데, 이를
재정의(override)
라고 한다.
- 상속받은 특성들을 재정의하려면 새로운 정의 앞에
override
키워드를 사용한다.
- 만약 자식클래스에서 부모클래스의 특성을 재정의했을 때, 부모클래스의 특성을 자식클래스에서 사용하고 싶다면
super
프로퍼티를 사용하면 된다.
- super 키워드를 메서드 내에서 사용한다면, 부모클래스의 타입 메서드와 타입 프로퍼티에 접근할 수 있으며 인스턴스 메서드 내에서 사용한다면, 부모클래스의 인스턴스 메서드와 인스턴스 프로퍼티, 서브스크립트에 접근 가능하다.
메서드 재정의
- 부모클래스의 메서드를 재정의하기 위해선 메서드 앞에
override
키워드를 사용한다.
- 부모클래스의 메서드를 재정의하더라도 부모클래스의 메서드 기능을 그대로 사용하고자 한다면 메서드 내부에서
super
프로퍼티를 사용하면 된다.
class Person {
var name: String = ""
var age: Int = 0
var introduction: String {
return "이름 : \(name), 나이 : \(age)"
}
func speak() {
print("가나다라마바사")
}
class func introduceClass() -> String {
return "인류의 소원은 평화입니다."
}
}
class Student: Person {
var grade: String = "F"
func study() {
print("Study hard")
}
override func speak() {
print("저는 학생입니다.")
}
}
class UniversityStudent: Student {
var major: String = ""
class func introduceClass() {
print(super.introduceClass())
}
override class func introduceClass() -> String {
return "대학생의 소원은 A+ 입니다."
}
override func speak() {
super.speak()
print("대학생이죠.")
}
}
print("----------yagom----------")
let yagom : Person = Person()
yagom.speak()
print("----------jay----------")
let jay: Student = Student()
jay.speak()
print("----------jenny----------")
let jenny: UniversityStudent = UniversityStudent()
jenny.speak()
print("----------Person.introduceClass----------")
print(Person.introduceClass())
print("----------Student.introduceClass----------")
print(Student.introduceClass())
print("----------UniversityStudent.introduceClass as String----------")
print(UniversityStudent.introduceClass() as String)
print("----------UniversityStudent.introduceClass as Void----------")
UniversityStudent.introduceClass() as Void
--result--
----------yagom----------
가나다라마바사
----------jay----------
저는 학생입니다.
----------jenny----------
저는 학생입니다.
대학생이죠.
----------Person.introduceClass----------
인류의 소원은 평화입니다.
----------Student.introduceClass----------
인류의 소원은 평화입니다.
----------UniversityStudent.introduceClass as String----------
대학생의 소원은 A+ 입니다.
----------UniversityStudent.introduceClass as Void----------
인류의 소원은 평화입니다.
- UniversityStudent 클래스에서 override가 있는 introduceClass() 메서드와 없는 introduceClass() 메서드를 살펴보자.
- UniversityStudent 클래스는 Person 클래스를 상속받았기 때문에 introduceClass()를 상속받아 사용가능하다. Override가 붙은 introduceClass()는 해당 introduceClass()메서드를 재정의한 것이고, override가 없는 것은 단순히 메서드 명칭만 같을 뿐 반환 타입이 다르기 때문에 상속받은 메서드가 아니다.
프로퍼티 재정의
- 부모클래스로부터 상속받은 인스턴스 프로퍼티나 타입 프로퍼티를 자식클래스에서 용도에 맞게 재정의 가능하다.
- 프로퍼티를 재정의할 때는
저장 프로퍼티로 재정의할 수는 없다.
- 프로퍼티를 재정의한다는 것은 프로퍼티 자체가 아니라 프로퍼티의
접근자(Getter)
, 설정자(Setter)
, 프로퍼티 감시자(Property Observer)
등을 재정의하는 것을 의미한다.
- 조상클래스에서
저장 프로퍼티
로 정의한 프로퍼티는 물론이고 연산 프로퍼티
로 정의한 프로퍼티도 접근자와 설정자를 재정의
할 수 있다.
- 프로퍼티를 상속받은 자식클래스에서는 조상클래스의 프로퍼티 종류(저장, 연산 등)는 알지 못하고
단지 이름과 타입만을 알기 때문
이다.
- 조상클래스에서
읽기 전용
프로퍼티였더라도 자식클래스에서 읽고 쓰기가 가능한 프로퍼티로 재정의
해줄 수도 있다.
- 그러나 읽기 쓰기 모두 가능했던 프로퍼티를 읽기 전용으로 재정의해줄 수는 없다.
- 읽기 쓰기가 모두 가능한 프로퍼티를 재정의할 때 접근자 설정자 모두 재정의해야 하며, 설정자만 재정의할 수는 없다.
- 만약 접근자에 따로 기능 변경이 필요 없다면 super.someProperty와 같은 식으로 부모클래스의 접근자를 사용하여 값을 받아와 반환해주면 된다.
class Person {
var name: String = ""
var age: Int = 0
var koreanAge: Int {
return self.age + 1
}
var introduction: String {
return "이름 : \(name), 나이 : \(age)"
}
}
class Student: Person {
var grade: String = "F"
override var introduction: String {
return super.introduction + ", " + "학점: \(self.grade)"
}
override var koreanAge: Int {
get {
return super.koreanAge
}
set {
self.age = newValue - 1
}
}
}
let yagom = Person()
yagom.name = "야곰"
yagom.age = 55
print(yagom.introduction)
print(yagom.koreanAge)
let jay: Student = Student()
jay.name = "제이"
jay.age = 14
jay.koreanAge = 15
print(jay.introduction)
print(jay.koreanAge)
- Person 클래스의 koreanAge 연산 프로퍼티는 읽기 전용이지만 자식클래스인 Student에서는 읽기-쓰기 연산프로퍼티로 재정의 하였습니다.
- Student 클래스에서 koreanAge 연산 프로퍼티 재정의시 접근자(getter)는 따로 기능을 재정의할 필요가 없어 부모클래스의 property를 그대로 사용하기 위해
super.koreanAge
작성
프로퍼티 감시자 재정의
- 프로퍼티 감시자도 프로퍼티의 접근자와 설정자처럼 재정의 가능하다.
- 마찬가지로 조상클래스에 정의한 프로퍼티가
연산 프로퍼티인지 저장 프로퍼티인지는 상관없다
. 다만 상수 저장 프로퍼티
나 읽기 전용 연산 프로퍼티
는 프로퍼티 감시자를 재정의 할 수 없다
.
- 왜냐하면 상수 저장 프로퍼티나 읽기 전용 연산 프로퍼티는 값을 설정할 수 없으므로 willSet이나 didSet 메서드를 사용한 프로퍼티 감시자를 원천적으로 사용할 수 없기 때문이다.
- 프로퍼티 감시자를 재정의하더라도
조상클래스에 정의한 프로퍼티 감시자도 동작
한다는 점을 잊지 말아야 한다.
프로퍼티 접근자
와 프로퍼티 감시자
는 동시에 재정의할 수 없다
. 만약 둘 다 동작하길 원한다면 재정의하는 접근자에 프로퍼티 감시자의 역할을 구현해야 한다.
class Person {
var name: String = ""
var age: Int = 0 {
didSet {
print("Person age: \(self.age)")
}
}
var koreanAge: Int {
return self.age + 1
}
var fullName: String {
get {
return self.name
}
set {
self.name = newValue
}
}
}
class Student: Person {
var grade: String = "F"
override var age: Int {
didSet {
print("Student age: \(self.age)")
}
}
override var koreanAge: Int {
get {
return super.koreanAge
}
set {
self.age = newValue - 1
}
}
override var fullName: String {
didSet {
print("FullName: \(self.fullName)")
}
}
}
let yagom = Person()
yagom.name = "야곰"
yagom.age = 55
yagom.fullName = "조야곰"
print(yagom.koreanAge)
let jay: Student = Student()
jay.name = "제이"
jay.age = 14
jay.koreanAge = 15
jay.fullName = "김제이"
print(jay.koreanAge)
--result--
Person age: 55
56
Person age: 14
Student age: 14
Person age: 14
Student age: 14
FullName: 김제이
15
- Student 클래스에는 Person 클래스의 age라는 저장 프로퍼티의 프로퍼티 감시자를 재정의 하였으며, koreanAge와 fullName이라는 연산 프로퍼티의 프로퍼티 감시자를 재정의.
- Person 클래스의 age라는 저장 프로퍼티에 이미 프로퍼티 감시자를 정의했으므로 jay의 age 프로퍼티의 값을 할당할 때는 Person에 정의한 프로퍼티 감시자와 Student에 정의한 프로퍼티 감시자가 모두 동작한다.
- 기존에 연산 프로퍼티로 정의했던 fullName 프로퍼티에도 프로퍼티 감시자를 정의하였다.
- 그러나 koreanAge 프로퍼티에는 프로퍼티 감시자와 프로퍼티 설정자를 동시에 정의할 수 없다.
재정의 방지
- 부모클래스를 상속받는 자식클래스에서 몇몇 특성을 재정의할 수 없도록 제한하고 싶다면 재정의를 방지하고 싶은 특성 앞에
final
키워드를 명시하면 된다.
- final 키워드가 명시된 특성을 재정의 할 경우
컴파일 오류
가 발생한다.
- 클래스를 상속하거나 재정의할 수 없도록 하고 싶다면 class 키워드 앞에 final 키워드를 명시하면 된다.