class Person {
var id = 0
var name = "이름"
var email = "abc@gmail.com"
}
class Student: Person {
// person의 저장속성은 모두 가지고 있다
// id, name, email을 가지고있다
var studentId = 0
}
class Undergraduate: Student {
// student의 저장속성은 모두 가지고 있다
// id, name, email, studentId를 가지고 있다
var major = "전공"
}
class Aclass {
func doSomething() {
print("Do something")
}
}
// 상속을 할때 기존에있던 doSomething을 "재정의"할 수 있다
// 재정의 하지 않으면 자동으로 doSomething을 가지고있다
class Bclass: Aclass {
override func doSomething() {
// 상위클래스에 있는 doSomething 함수를 실행한다
// 재정의하기전의 함수를 실행한다는 뜼
super.doSomething()
print("Do another job")
}
}
class SomeSuperclass {
// 저장속성
var aValue = 0
// 메서드
func doSomething() {
print("Do something")
}
}
class SomeSubclass: SomeSuperclass {
// 저장속성의 재정의는 원칙적 불가
//override var aValue = 3 **error발생**
// 저장속성을 계산속성으로 재정의는 가능하다
override var aValue: Int {
get {
return 1
}
set {
// ⭐️**self로 쓰면 안됨**
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
// 1) 저장속성 계산속성으로 재정의(메서드 추가) 가능
// self키워드 대신 super키워드 사용해야함 - 메모리주소를 가지고 있기 때문에
override var currentSpeed: Double {
// 상위 속성이기 때문에 super키워드 필요
get {
return super.currentSpeed
}
set {
super.currentSpeed = newValue
}
}
// 1) 저장속성에 속성감시자를 추가하는 재정의(메서드 추가)는 가능
override var currentSpeed: Double {
// 상위 속성이기 때문에 super키워드 필요
willSet {
print("값이 \(currentSpeed)에서 \(newValue)로 변경 예정")
}
didSet {
print("값이 \(oldValue)에서 \(currentSpeed)로 변경 예정")
}
}
⭐️ 계산속성을 재정의 가능 (super키워드 주의)
// 계산속성에서 set블럭이 속성감시자의 역할을 하기때문에 **속성감시자 추가 불가
// 계산속성의 재정의 첫번째 케이스인데 이미 set이 있어서 속성감시자 추가 불가**
override var halfSpeed: Double {
get {
return super.currentSpeed / 2
}
set {
super.currentSpeed = newValue * 2
}
}
⭐️ 계산속성을 재정의 하면서, 속성감시자 추가 가능 (속성감시자 부분 참고)
// 계산속성에서 set블럭이 속성감시자의 역할을 하기때문에 **속성감시자 추가 불가**
// 하지만 읽기/쓰기 계산속성을 재정의하는 케이스에서는 **예외**
override var halfSpeed: Double {
willSet {
print("값이 \(halfSpeed)에서 \(newValue)로 변경 예정")
}
didSet {
print("값이 \(oldValue)에서 \(halfSpeed)로 변경 예정")
}
}
}
class Color {
//let red, green, blue: Double
// 동일한 타입일때, 한줄에 작성가능
let red: Double
let green: Double
let blue: Double
// **생성자도 오버로딩(Overloading)을 지원**
// (파리미터의 수, 아규먼트 레이블, 자료형으로 구분)
// 기본 생성자 -> 저장 속성의 기본값을 설정하면 "자동" 구현이 제공됨
init() {
red, green, blue = 0.0
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
var color = Color.init() 은 Color() 과 똑같은거임
struct Color1 {
var red: Double = 1.0
var green: Double = 1.0
var blue: Double
}
// 두개의 이니셜라이즈를 자동으로 제공
var c2 = Color1(blue: Double)
var c2 = Color1(red: Double, green: Double, blue: Double)
case1) 올바르지 않은 방법
이유 : 새로운 생성자를 매번 만들어줘야함
struct Color {
let red, green, blue: Double
init(white: Double) {
red = white
green = white
blue = white
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
case2) 올바른방법
이유 : 재사용성을 고려한 방법
struct Color1 {
let red, green, blue: Double
// 구조체는 다른 생성자를 호출하는 방식도 가능 ⭐️
init() {
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
init(white: Double) {
self.init(red: white, green: white, blue: white)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
class Color2 {
let red, green, blue: Double
// 편의생성자 : **생성자의 재사용성을 고려해 다른 생성자를 호출할 수 있음**
// **class는 생성자의 재정의를 고려해야하는데 편의생성자는 고려를 안해도 됨**
convenience init() {
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
convenience init(white: Double) {
self.init(red: white, green: white, blue: white)
}
// 지정생성자
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
💡 **생성자를 만들때 고려해야하는게 재사용성이다(오버로딩이 되기때문에)
근데 여러개의 생성자를 만들때 다른 생성자를 호출할수있는데
구조체의 경우엔 그냥 생성자안에 다른생성자를 사용할 수 있지만
클래스의 경우엔 편의생성자를 만들어야 다른생성자를 사용(호출)할 수 있다.**
(이미 모든 속성을 초기화하는 지정생성자가 있다면)
모든 속성을 초기화하지 않는 경우 편의생성자로 구현을 권장
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)
}
}
var a = Aclass()
// 상속이 일어나는 경우 ⭐️
// **init**을 해야 메모리에 초기화가 된다(convinence X)
class Bclass: Aclass {
var z: Int
// 생성자의 역할
// 나의단계에있는 저장속성(z) 찍어내고
// 상위지정생성자 호출해서 상위단계 저장속성(x, y)을 찍어냄
init(x: Int, y: Int, z: Int) {
// ⭐️ (필수) - 새로운 저장속성을 초기화 해준다
self.z = z
// error발생 : 상위의 지정생성자 호출하기전이기 때문에
//self.y = y
// ⭐️ (필수) 상위의 지정생성자 호출 - **어떤거든**
super.init(x: x, y: y)
// 새로운지정생성자 호출 -> 상위의지정생성자 호출 -> 하고싶은거
// self.z = 7
}
convenience init(z: Int) {
//self로 접근이 불가능함 -> 아직 메모리를 초기화 시키지 않았음
//self.z = 7
self.init(x: 0, y: 0, z: z)
}
convenience init() {
self.init(z: 2)
}
}
[1단계 - 상위 생성자에 대한 고려]
상위에 어떤 지정 생성자가 존재하는지?
(상위) 지정 생성자
1) 하위클래스에서 지정 생성자로 구현 (재정의)
2) 하위클래스에서 편의 생성자로 구현 가능 (재정의)
3) 구현 안해도됨(반드시 재정의하지 않아도 됨), 새로운생성자를 만듦
1) 재정의를 하지 않아도 됨 (호출 불가가 원칙이기 때문에 재정의 제공 안함)
2) (만약에 동일한 이름을 구현했다면) 그냥 새로 정의한 것임
SuperClass
class Aclass {
var x = 0
// init() {} // 기본 생성자가 자동으로 제공됨
}
1단계 - 상위생성자에 대한 고려
⭐️상위에는 "지정"생성자가 존재함
선택지가 3가지 존재함(지정생성자 재정의, 편의생성자 재정의, 새로운 생성자)
class Bclass: Aclass {
var y: Int
첫번째 선택지 - 지정생성자 재정의
override init() {
2단계 - 현재단계(Bclass)의 저장속성고려하여 구현
재정의이긴하지만 결국은 지정생성자이고 "하위클래스에서의 지정생성자"
-> 현재단계 저장속성 초기화 -> 상위클래스 지정생성자 호출
self.y = 0
super.init()
}
}
1단계 - 상위생성자에 대한 고려
⭐️상위에는 "지정"생성자가 존재함
선택지가 3가지 존재함(지정생성자로 재정의, 편의생성자로 재정의, 새로운 생성자)
class Bclass: Aclass {
var y: Int
두번째 선택지 - 편의생성자로(똑같은 이름 + convenience)
편의 생성자는(델리게이트 크로스) 현재단계의 지정생성자를 호출해야함
override convenience init() {
self.init(y: 0)
}
현재단계의 하위클래스의 지정생성자
init(y: Int) {
self.y = y
super.init() // x초기화 시켜줌
}
}
1단계 - 상위생성자에 대한 고려
⭐️상위에는 "지정"생성자가 존재함
선택지가 3가지 존재함(지정생성자로 재정의, 편의생성자로 재정의, 새로운 생성자)
class Bclass: Aclass {
var y: Int
세번째 선택지 - 새로운 생성자 생성
init(y: Int) {
self.y = y
super.init() // x초기화 시켜줌
}
}
SuperClass
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
⭐️상위의 생성자
지정생성자, 편의생성자
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)
}
// convenience init() { } // 자동 상속 (예외 규칙)
}
class Aclass {
var x: Int
required init(x: Int) {
self.x = x
}
}
class Bclass: Aclass {
// required init(x: Int) {
// super.init(x: x)
// }
}
class Cclass: Aclass {
init() {
super.init(x: 0)
}
// 다른생성자를 구현했기때문에 required init이 자동상속되지 않음
required init(x: Int) {
// 호출시 required init으로 호출하지 않음
super.init(x: x)
}
}
struct Animal {
let species: String
// 실패가능 생성자
init?(species: String) {
if species.isEmpty {
**// 생성자 내에서 실패 가능 부분에 nil을 리턴하면 됨(약속)**
return nil
}
self.species = species
}
// 실패가능한생성자가 있을 때 같은 이름의 지정생성자 구현 불가
init(species: String) {}
}
let a = Animal(species: "Giraffe")
let b = Animal(species: "")
enum TemperatureUnit {
case kelvin
case celsius
case fahrenheit
// 실패가능생성자 enum에서 생성자를 구현할경우 고민해봐야함
init?(symbol: Character) {
switch symbol {
case "K":
self = TemperatureUnit.kelvin
case "C":
self = TemperatureUnit.celsius
case "F":
self = TemperatureUnit.fahrenheit
// return이 nil인 경우가 있기때문에 실패가능생성자로 구현해야함
default:
return nil
}
}
}
let c: TemperatureUnit = TemperatureUnit.celsius
let f: TemperatureUnit? = TemperatureUnit(symbol: "F")
유사한방식 - 열거형의 원시값 설정
enum TemperatureUnit1: Character {
case kelvin = "K"
case celsius = "C"
case fahrenheit = "F"
}
let f1: TemperatureUnit1? = TemperatureUnit1(rawValue: "F") // .fahrenheit
let u: TemperatureUnit1? = TemperatureUnit1(rawValue: "X") // nil
같은 구조체나 클래스 내에서 (델리게이트 어크로스)
클래스의 상속관계에서
클래스의 재정의에서