4주차 정리
✅ 클래스의 상속
1️⃣ 상속과 초기화
- 기본적으로 상속은 데이터(저장 속성)을 추가하는 관점에서 생각
- 저장 속성은 메모리 공간을 가짐
- 메모리가 하나씩 더 추가되는 것
- Base class : 어떤 것도 상속하지 않는 것
- 다중 상속 : 불가능
- 클래스만 상속 가능!! 구조체 불가
final
: 상속이 불가능하다는 의미. 멤버 앞에 붙이면 재정의 불가능하다는 의미.
재정의
: 상위 클래스에서 존재하는 멤버를 변형
- 저장 속성은 재정의 불가. 왜냐면 가리키고 있는 메모리 구조이기 때문!
- UIKit : Cocoa touch framework의 UI를 담당하는 녀석
- 메서드 부분은 배열로 이루어져 있음!
- 저장 속성과 메서드는 메모리 구조가 완전 다름
2️⃣ 상속과 재정의
- 저장 속성만 재정의가 불가능! 데이터 구조이기 때문에. 상위의 데이터구조는 하위에서 절대 변형시킬 수 없음.
- 재정의하지 않으면 자동으로 그 해당 함수를 가지고 있는 것
- 부모클래스와 실제로 하는 일이 조금 다른 것
- 메서드는 항상 배열로 존재
🌟 저장 속성 vs 메서드 : 저장 속성은 메모리 주소를 가리키는 구조여서 재정의 불가. 메서드는 배열로 된 테이블을 다시 만들기 때문에 재정의가 가능. 메서드를 재정의 안 한다고 가정하면 메모리 주소를 그냥 가져오는 것. 외우지 말고 당연하게 받아들이기.
😎 정리
- 대원칙 1 : 저장 속성 재정의 불가 -> 메모리 구조에서 상위 구현을 참조하기 때문
- 대원칙 2 : 메서드는 재정의 가능(기능 확장만 가능)
- 계산 속성 재정의 가능.
class SomeSuperclass {
var aValue = 0
func doSomething() {
print("Do something")
}
}
class SomeSubclass: SomeSuperclass {
override var aValue: Int {
get {
return 1
}
set {
super.aValue = newValue
}
}
override func doSomething() {
super.doSomething()
print("Do something 2")
}
}
class Vehicle {
var currentSpeed = 0.0
var halfSpeed: Double {
get {
return currentSpeed / 2
}
set {
currentSpeed = newValue * 2
}
}
}
class Bicycle: Vehicle {
var hasBasket = false
override var currentSpeed: Double {
get {
return super.currentSpeed
}
set {
super.currentSpeed = newValue
}
}
override var currentSpeed: Double {
willSet {
print("값이 \(currentSpeed)에서 \(newValue)로 변경 예정")
}
didSet {
print("값이 \(oldValue)에서 \(currentSpeed)로 변경 예정")
}
}
override var halfSpeed: Double {
get {
return super.currentSpeed / 2
}
set {
super.currentSpeed = newValue * 2
}
}
override var halfSpeed: Double {
willSet {
print("값이 \(halfSpeed)에서 \(newValue)로 변경 예정")
}
didSet {
print("값이 \(oldValue)에서 \(halfSpeed)로 변경 예정")
}
}
}
3️⃣ 메모리 구조를 통한 이해
- 저장 속성과 메서드를 분리해서 생각.
- 저장 속성 : 본인 고유의 데이터 메모리 영역을 가지고 있음.
- 메서드 : 인스턴스에 존재하는 것이 아님. 메서드를 실행했을 때 원래 클래스(데이터 영역)를 찾아 감. 함수의 메모리 주소를 찾음. 코드 영역으로 가서 명령어 실행. 항상 단계 별로 새로운 배열을 가짐.
✅ 클래스의 초기화와 생성자
- 생성자 오버로딩 가능
- 오버로딩하는 이유 : 개발자에게 여러 가지 선택권을 줄 수 있음. 자유도 생김.
1️⃣ 멤버와이즈 이니셜라이저 - 구조체의 특별한 생성자
- 생성자를 만들지 않아도 알아서 만들어줌. 일부, 전체 속성을 새로운 값으로 설정
2️⃣ 지정 생성자
- init( .... ) 형태를 가지는 생성자
- 지정생성자는 모든 저장 속성을 초기화해야함
- 생성자를 1개이상 구현하면 기본 생성자를 제공하지 않음
3️⃣ 편의 생성자
- 지정 생성자에 의존 및 호출
- 실질적으로 가능한 지정생성자의 갯수를 줄이고, 편의생성자에서 지정생성자 호출하도록 하는 것이 바람직
- 상속했을때, 편의생성자의 경우 서브클래스에서 재정의를 못함
class Aclass {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
convenience init() {
self.init(x: 0, y: 0)
}
}
class Bclass: Aclass {
var z: Int
init(x: Int, y: Int, z: Int) {
self.z = z
super.init(x: x, y: y)
}
convenience init(z: Int) {
self.init(x: 0, y: 0, z: z)
}
convenience init() {
self.init(z: 0)
}
func doSomething() {
print("Do something")
}
}
let a = Aclass(x: 1, y: 1)
let a1 = Aclass()
let b = Bclass(x: 1, y: 1, z: 1)
let b1 = Bclass(z: 2)
let b2 = Bclass()
4️⃣ 지정 생성자와 편의 생성자
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
namedMeat.name
let mysteryMeat = Food()
mysteryMeat.name
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
✅ 확장(Extension)
1️⃣ 상속과 확장의 비교
- 수직 확장 vs 수평 확장
- 수직 확장 : 상속(클래스만 가능), 데이터 추가, 기능 변형.
- 수평 확장 : 현재 존재하는 타입에 기능(메서드)을 추가하여 사용.
- 확장은 본질적으로 기존의 메서드 테이블 이외에 메서드를 추가하는 것 -> direct dispatch
- 확장한 메서드는 상속 시 재정의 불가.
- 클래스의 경우 편의생성자만 추가 가능
- 값타입(구조체, 열거형)의 경우, 지정 생성자 형태로도 (자유롭게) 생성자 구현 가능