상속?
- 클래스에만 해당
- 기반 클래스를 다른 클래스에서 물려받는 수직 확장
1) 상속은 똑같은 기능을 구현하기 위해서 코드를 다시 작성할 필요가 없어 코드 재사용이 용이
2) 더불어 기능 확장시에 기존 클래스를 변경하지 않고도 새로운 추가 기능을 구현 및 정의할 수 있다.
- 용어 : 부모클래스, 자식클래스, 기반클래스(상속을 받지 않은 클래스, 기본)
- 부모 클래스로부터 물려받은
- 프로퍼티, 메소드, 서브스크립트 사용 가능
- 재정의도 가능
- 프로퍼티 감시자도 가능 (부모클래스의 프로퍼티가 연산이든 저장이든)
class 이름: 부모클래스 이름 {
}
재정의
자식 클래스가 부모 클래스로부터 물려받은 특성을 그대로 사용하지 않고 자신만의 기능으로 변경 & 사용
-
상속받은 특성을 재정의 하려면 override 키워드 사용
→ 컴파일러가 조상들을 확인 후 재정의 함, 확인했는데 없으면 컴파일 오류
-
자식클래스가 부모클래스의 특성을 사용하고 싶다면 super 프로퍼티 사용.
- 타입 메서드 안에서 사용하면, 타입 메서드나 타입 프로퍼티에 접근
- 인스턴스 메서드 안에서 사용하면, 인스턴스 메서드나 인스턴스 프로퍼티, 서브스크립트에 접근
메서드 재정의
- 상속받은 인스턴스 메서드, 타입 메서드를 자식 클래스에서 용도에 맞도록 재정의 할 수 있다.
- 동일하게 override를 사용
단 메서드의 반환 타입이나 매개변수가 다르면 서로 다른 메서드로 취급하니 주의!
프로퍼티 재정의
-
상속받은 인스턴스, 타입 프로퍼티 역시 재정의 할 수 있다.
프로퍼티를 재정의한다? 저장 프로퍼티 X → 접근자, 설정자, 감시자 등을 재정의하는 것을 의미
-
자식은 조상의 프로퍼티의 이름과 타입만 안다.
- 그래서 이 둘을 일치해야 한다.
- 그렇기 때문에 조상 프로퍼티가 저장인지 연산인지 중요X
조상 클래스의 저장이든 연산이든 접근자와 설정자를 재정의 할 수 있다.
super.someProperty를 사용해서 부모클래스의 접근자를 사용하여 값을 받을 수 있다.
ex) 조상의 읽기 전용 프로퍼티? → 자식에서 읽고 쓰기 가능한 프로퍼티로 재정의 가능
*** 조상의 읽기 쓰기 프로퍼티 → 읽기 전용은 불가능, 접근자와 설정자 모두 재정의 해야 한다.
프로퍼티 감시자 재정의
- 조상 클래스에 정의한 프로퍼티가 연산, 저장? 상관없이 감시자 재정의 가능
- 상수 저장, 읽기 전용 프로퍼티는 값을 설정할 수 없기 때문에 willSet, didSet이 원칙적으로 사용 불가
→ 그래서 재정의 할 수 없음
- 자식에서 감시자를 재정의 해도 부모도 작동, 자식도 작동
- 프로퍼티 접근자와 감시자는 동시에 재정의 불가능.
둘 다를 원한다면 재정의하는 접근자에 프로퍼티 감시자 역할을 구현해야 한다.
서브스크립트 재정의
- 당연히 매개변수와 반환값이 동일해야 재정의
- 마찬가지로 override
재정의 방지
- 클래스 혹은 특성들이 재정의 되는 것을 방지하고 싶을 때 final 키워드
- final class, final func, final var, final subscript ...
클래스 이니셜라이저, 그리고 상속과 재정의
값 타입의 이니셜라이저는 이니셜라이저 위임을 위해 이니셜라이저끼리 구분할 필요가 없었지만 클래스에서는 지정 이니셜라이저와 편의 이니셜라이저로 역할을 구분한다. 값 타입은 상속을 고려할 필요가 없지만 클래스는 상속이 가능하므로 상속과의 관계, 재정의도 고려해야 한다.
지정 이니셜라이저 & 편의 이니셜라이저
- 지정 이니셜라이저 : 클래스의 주요 이니셜라이저. 필요에 따라 부모클래스의 이니셜라이저를 호출 가능, 이니셜라이저가 정의된 클래스의 모든 프로퍼티를 초기화해야 하는 임무. 그래서 하나 이상이 있어야 함. 필수
- 편의 이니셜라이저 : 매개변수가 너무 많거나 초기값 지정을 해줘야 할 때 초기화를 좀 더 손쉽게 도와주는 역할. 지정 이니셜라이저를 자신의 내부에 호출. 필수는 아님
클래스 초기화 위임
지정 이니셜라이저와 편의 이니셜라이저 사이의 관계에 적용되는 3가지 규칙
- 자식 클래스의 지정 이니셜라이저는 부모 클래스의 지정 이니셜라이저를 반드시 호출해야 한다.
- 편의 이니셜라이저는 자신을 정의한 클래스의 다른 이니셜라이저를 반드시 호출해야 한다.
- 편의 이니셜라이저는 궁극적으로 지정 이니셜라이저를 반드시 호출 해야 한다.
2단계 초기화
스위프트 클래스의 초기화는 2단계를 거친다.
1단계
클래스에 정의한 각각의 저장 프로퍼티에 초기값이 할당 → 2단계 돌입
- 클래스가 지정 혹은 편의 이니셜라이저 호출
- 그 클래스의 새로운 인스턴스를 위한 메모리가 할당, 메모리가 초기화되지는 않은 상태
- 지정 이니셜라이저 → 클래스에 정의된 모든 저장 프로퍼티가 값이 있는지 확인 → 메모리 초기화
- 지정 이니셜라이저는 부모 클래스의 이니셜라이저가 같은 동작을 행할 수 있도록 초기화를 양도
- 상속 체인을 따라 최상위 클래스에 도달할 때까지 이 작업을 반복
- 최상위 클래스에 도달했을 때 → 모든 저장 프로퍼티에 값이 있다고 확인, 메모리 초기화 완료
2단계
반대로 상속 체인을 내려오면서 지정 이니셜라이저들이 인스턴스를 제 각각 정의.
- self, 메서드, 프로퍼티에 접근 가능
- 마지막으로 편의 이니셜라이저를 통해 self.를 통한 사용자 정의 작업을 진행할 수 있음.
2단계로 오기까지의 4단계 안전 장치
-
자식클래스의 지정 이니셜라이저가 부모 클래스의 이니셜라이저를 호출하기 전에
자신의 프로퍼티를 모두 초기화 했는지 확인.
```swift
class Student {
var major: String
override init(name: String, major: String) {
self.major = major
// 나중에 호출
super.init(name: name)
}
}
```
-
자식클래스의 지정 이니셜라이저는 상속받은 프로퍼티에 값을 할당하기 전에
반드시 부모 클래스의 이니셜라이저를 호출해야 한다.
-
편의 이니셜라이저는 자신의 클래스에 정의한 프로퍼티를 포함하여 그 어떤 프로퍼티라도 값을 할당하기 전에
다른 이니셜라이저(지정, 혹은 지정과 연결되어 있는 다른 편의)를 호출해야 한다.
class Student {
var major: String
convenience init(name: String, major: String, grade: String) {
self.init(name: name, major: major, grade: grade)
self.grade = grade
}
}
-
1단계를 마치기 전까지는
이니셜라이저에서 인스턴스 메서드, 인스턴스 프로퍼티, self 프로퍼티 호출 및 읽기, 활용이 불가능하다.
이니셜라이저 상속 및 재정의
기본적으로 swift의 이니셜라이저는 부모클래스의 이니셜라이저를 상속받지 않는다.
(자식클래스에 최적화X → 완전, 정확한 초기화가 어려움 , 반면 완전 정확? → 자동 상속)
- 똑같은 지정 이니셜라이저 사용? → override로 재정의 (지정을 지정으로, 지정의 편의로 재정의 가능)
- 똑같은 편의 이니셜라이저 사용? → override X (자식에서 부모의 편의 init를 호출X)
- 실패 가능한 이니셜라이저 사용? → override O → 실패 가능으로 해도 되고 아니어도 된다.
이니셜라이저 자동 상속
본질적으로 swift의 이니셜라이저는 부모클래스의 이니셜라이저를 상속받지 않는다. 하지만 특정 조건이 만족하면 자동으로 상속한다.
자식클래스에서 프로퍼티 기본값을 모두 제공한다고 가정할 때,
-
첫 번째 조건, 자식클래스에서 별도의 '지정' 아니셜라이저를 구현하지 않았을 때
- 지정, 편의 다 자동 상속
- 이 경우 자식에 '편의'가 있어도 상관없이 자동 상속된다.
-
두 번째 조건, 규칙1에 따라 자동 상속 받거나 부모클래스의 지정 이니셜라이저를 모두 재정의해서 동일한 지정 이니셜라이저를 모두 사용할 수 있을 때
- '편의' 이니셜라이저가 자동 상속
- '부모의 지정'을 '편의'로 재정의해도 자동 상속
요구 이니셜라이저
- required 수식어 사용해서 지정 → 자식 클래스에서 반드시 해당 이니셜라이저를 구현해야 함
- 자식클래스에서 구현시 override 대신 required
- 자동 상속의 경우는 구현 안해도 된다.