[Swift] 상속과 초기화

임승섭·2023년 5월 31일
0

Swift

목록 보기
7/35

클래스의 상속

  • 유일하게 클래스에서만 지원한다

기본 개념

  • 수직 확장
    • 본질적으로 성격이 비슷한 타입을 새로 만들어
      1). 데이터(저장속성)를 추가(즉, 메모리가 추가)하거나
      - 저장 속성은 메모리 공간을 갖는다
      2). 기능(메서드)를 변형시켜서 사용
  • 본질적으로 새로운 타입을 만드는 것이지만
  • 기본적으로 데이터(저장 속성)을 추가하는 관점에서 생각한다
  • 저장 속성 : 기본적으로 메모리 공간을 가지고 있음.
class Person {	// 3개의 저장 속성
	var id = 0
    var name = "이름"
    var email = "abc@gmail.com"
}

class Student: Person {	// Person 클래스를 상속한다
	var studentId = 0
}	// 실제로는 Person의 3가지 속성도 가지고 있다

class Undergraduate: Student {
	var major = "전공"
}	// 자동으로 저장 속성 4개가 포함된다

var person1 = Undergraduate()
person1.id
person1.studentId
// 4개의 저장 속성에 접근 가능
  • 위에 아무것도 상속하지 않은 클래스 (처음 클래스) : base class
  • 상대적으로 child class/parent class, sub class/super class
  • swift에서는 다중상속을 지원하지 않는다

final

  • class 선언 시 final을 붙이면 상속할 수 없다
  • 멤버 앞에 final을 붙이면, 재정의가 불가능하다
    (재정의 : 상위 클래스에 존재하는 멤버를 변형하는 것)

재정의 (Overriding)

  • "재정의가 불가" <- 기존 메모리 공간을 그대로 유지해야 하기 때문
  • overloading : 함수에서 하나의 이름에 여러 함수를 대응시켜서 사용
  • overriding : 재정의. 클래스의 상속에서 상위클래스의 속성/메서드의 기능을 약간 변형하여 사용하는 것
  • 재정의 : sub class에서 super class의 동일한 멤버를 변형하여 구현한다
  • 재정의 : 상위 클래스에서 존재하는 멤버를 변형하는 것
  • 재정의가 가능한 대상 (각각 재정의 방식이 다르다)
    • 속성 (저장 속성은 불가능 -> 데이터 구조의 변형 불가)
      • 데이터(저장 속성)는 추가만 가능
      • 계산 속성, ... 가능
        • 계산 속성은 실질적으로 메서드이기 때문에 메서드쪽에 더 가까움
    • 메서드 (메서드, 서브스크립트, 생성자 -> 기능의 변형 가능)
      • 기능(메서드)는 추가 및 변형(대체) 가능
      • 상위 구현을 호출하는 경우도 많다 (super.~)
    • 메모리 구조가 다르기 때문. (공간 차지 vs 주소 저장) (강의자료 참고)
      • 상속 과정에서 저장 속성은 이전 super class의 데이터를 가리킴
      • 하지만 메서드들은 계속 새로운 배열을 생성
class SomeSuperclass {
	// 저장속성
    var aValue = 0
    
    // 메서드
    func doSomething() {
    	print("Do something")
    }
}

class SomeSubclass: SomeSuperclass {
	
    ** override var aValue = 3	// 저장 속성의 재정의는 불가
    
    // 저장속성을 계산속성으로 재정의 가능. 메서드 형태로 부수적 추가 가능.
    // 계산속성 : 실질적인 메서드
    // 즉, 메서드 형태로 새롭게 정의하는 건 메모리 구조를 건드리지 않는다
    override var aValue: Int {
    	get {
        	return 1
        }
        set {		// 주의 : self.aValue라고 쓰면 안된다
        	super.aValue = newValue
        }
    }
    
    // 메서드는 자유롭게 재정의 가능 (변형 / 대체)
    override func doSomething() {
    	super.doSomething()		// 상위 클래스의 doSomething 실행. (상위의 메서드 호출)
        print("Do something 2")	// 그 다음에 이것도 실행
        // super.doSomething()	// 여기에다가 써도 됨
    }
}

재정의 방식

속성의 재정의 (엄격)

  • 실질적으로 타입 속성을 재정의하는 일은 매우 드물다. 일단 배제하고 생각하자
  • 저장 속성의 재정의
    • 원칙적으로 불가능 (고유의 메모리 공간 유지 필요)
      • 하위 클래스에서 메모리 공간을 바꾸는 방식으로의 재정의는 불가능
    • 메서드 형태로 추가하는 방식의 재정의 가능
      • 읽기/쓰기 가능한 계산 속성으로 재정의 가능.
        (기능 축소는 불가능. read-only로 불가능)
        • 기존 저장 속성을 생각해보면, 읽기/쓰기가 모두 가능하다
        • 따라서 이걸 읽기만 가능한 계산 속성으로 기능을 축소하는 건 불가능하다
      • 속성 감시자 추가 가능 (메서드)
        • 단순히 저장 속성이 변하는 시점을 관찰할 뿐
class Vehicle {
	// 저장 속성
	var currentSpeed = 0.0
    
    // 계산 속성 (실질적 메서드)
    var halfSpeed: Double {
    	get {
        	return currentSpeed / 2
        }
        set {
        	currentSpeed = newValue * 2
        }
    }
}

class Bicycle: Vehicle {
	
    // 저장 속성 추가
    var hasBasket = false
    
    /*1*/ 저장 속성을 재정의 -> 계산 속성으로 재정의했다
    override var currentSpeed: Double {
    	
        // 상위 속성을 이용하기 때문에 super. 붙여주기
        get {
        	return super.currentSpeed
        }
        set {
        	super.currentSpeed = newValue
        }
    }
    
    /*2*/ 저장 속성을 재정의 -> 속성 감시자를 추가하는 재정의
    // -> 저장 속성의 메모리 구조를 건드리지 않고, 메서드를 추가하는 것
    override var currentSpeed: Double {
    	
        // 상위 속성을 이용하기 때문에 super. 붙여주기
        willSet {
        	print("값이 \(currentSpeed)에서 \(newValue)로 변경 예정")
        }
        didSet {
        	print("값이 \(oldValue)에서 \(currentSpeed)로 변경 예정")
        }
    }
    
    /*1*/ 계산 속성을 재정의
    override var halfSpeed: Double {
   		get {
        	return super.currentSpeed / 2
        }
        set {
        	super.currentSpeed = newValue * 2
        }
    }
    
    /*2*/ 계산 속성을 재정의 + 속성 감시자 추가
    // 앞에서, 계산 속성에서는 속성 감시자 추가할 수 없다고 배웠다.
    // 예외적으로 상속해서 재정의하는 경우에는 속성 감시자를 추가할 수 있다!!
    override var halfSpeed: Double {
    	willSet {
        	print("값이 \(halfSpeed)에서 \(newValue)로 변경 예정")
        }
        didSet {
	        print("값이 \(oldValue)에서 \(halfSpeed)로 변경 예정")
        }
}

메서드(계산 속성)의 재정의

  • 기능의 범위 축소하는 재정의는 불가능
    • 읽기 전용 -> 읽기/쓰기 가능한 속성으로 재정의 가능 (확장o)
      • 속성 감시자 추가하는 재정의는 불가능
        • 읽기 전용 속성은 어차피 값이 변할 일이 없다
        • 따라서 속성 감시자를 추가한다는 게 논리적으로 맞지 않음
    • 읽기/쓰기 -> 읽기만 가능한 속성으로 제정의 불가능 (기능 제한x)
      • 속성 감시자 추가하는 재정의 가능 (관찰 가능)
class Vehicle1 {
	// 저장 속성
	var currentSpeed = 0.0
    
    var datas = ["1", "2", "3", "4", "5"]
    
    // 메서드
    func makeNoise() {
        print("경적을 울린다.")
    }
    
    // 서브스크립트 (특이한 이름의 '메서드')
    subscript(index: Int) -> String {
        get {
            if index > 4 {
                return "0"
            }
            return datas[index]
        }
        set {
            datas[index] = newValue
        }
    }
}

class Bicycle1: Vehicle1 {
	/*(순서) 1*/ 상위 -> 하위 호출 가능
    override func makeNoise() {
    	super.makeNoise()
        print("자전거가 지나간다고 소리친다")
    }
    
    
    /*(순서) 2*/ 하위 -> 상위 호출 가능
    override func makeNoise() {
    	print("자전거가 지나간다고 소리친다")
        super.makeNoise()
    }
    
    /*(순서) 3*/ 상위 아예 무시 가능
    override func makeNoise() {
    	print("경적을 울리고, 자전거가 지나간다고 소리친다.")
    }
    
    
    // 서브스크립트 재정의 당연히 가능 (메서드니까)
    overide subscript(index: Int) -> String {
    	get {
        	if index > 4 {
            	return "888"
            }
            return super[index]		// super 뒤에 바로 대괄호 붙여도 사용 가능하다
            // -> 이건 그냥 상위에 있는 서브스크립트를 실행시키는 문법
        }
        set {
        	supet[index] = newValue
        }
    }
}

let v = Bicycle1()
v.currentSpeed
v.makeNoise()

인스턴스 속성의 대원칙

  • 저장 속성 재정의는 원칙적으로 불가능,
    but 메서드 방식으로 추가는 가능
  • 계산 속성의 유지/확장은 가능, 축소는 불가능
  • 속성 감시자(메서드)를 추가하는 재정의는 언제나 가능,
    but 읽기전용 계산 속성을 관찰하는 건 의미 없으므로 불가능

초기화 (Initialization)

  • class, struct, enum에서 인스턴스를 생성하는 과정. 메모리에 찍어내는 과정
  • 저장 속성에 대한 초기값 설정
    -> 인스턴스를 사용가능한 상태로 만든다
  • 생성자의 실행이 완료되면, 인스턴스의 모든 저장속성이 초기값 가지도록 하는 것
    -> 생성자의 역할
  • 초기화의 방법
    • 저장 속성의 선언과 동시에 값 저장
    • 저장 속성을 옵셔널로 선언 (초기값 없어도 nil로 초기화)
    • 생성자에서 값 초기화
  • 1, 2번 방법이면 생성자 구현하지 않아도 초기화 가능

생성자 구현

class Color {
    
    let red: Double
    let green: Double
    let blue: Double
    
    
    // 생성자도 오버로딩 가능하다. 파라미터 종류로 구분한다
    
    init() {      // "init()" -> 기본 생성자. 저장 속성의 기본값을 설정하면 자동 구현 (따로 구현하지 않아도 알아서 자동 구현을 제공)
        red = 0.0
        green = 0.0
        blue = 0.0
    }

    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
    }
    
    // 여러 가지 방법의 생성자가 있는 이유 -> 다양한 선택권
}

// 다양한 선택권
var color = Color()   
var color2 = Color.init()

color = Color(white: 7.0)
color = Color.init(white: 7.0)

color = Color(red: 7.0, green: 7.0, blue: 7.0)
color = Color.init(red: 7.0, green: 7.0, blue: 7.0)

Memberwise Initializer

  • 구조체의 특별한 생성자 (자동 제공)
struct Color1 {	// 기본값이 없어도 에러가 나지 않는다
    var red: Double
    var green: Double
    var blue: Double
    
    
    /* 이게 알아서 제공이 된다
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    */
}

color1 = Color1(red: 1.0, green: 1.0, blue: 1.0) // 자동으로 각각 입력할 수 있게 뜬다

// 만약 몇 개만 기본값을 준다면, 기본값 없는 파라미터만 변수 입력할 수 있도록 생성자가 나옴.
  • 생성자를 구현하지 않으면, 컴파일러가 기본 생성자를 자동으로 생성 -> "init()"
  • 생성자를 구현하면, 기본 생성자를 자동으로 생성하지 않는다
  • 구조체는 저장 속성들이 기본값을 가지고 있어도(다 가지고 있지 않아도),
    추가적으로 Memberwise Initializer 자동으로 제공
  • 직접적으로 생성자 구현하면,
    Memberwise Initializer 제공하지 않는다

0개의 댓글