[Swift] 상속, 프로퍼티 오버라이딩 완전정복

wooje·2023년 9월 12일
0
post-thumbnail

평소에 노션에 필기한 내용들은 #TodayILearned 해시태그를 붙여 여기 벨로그에 공유하려고 합니다...! 구글이라는 우주에 우주쓰레기마냥 흩어져 있는 개념들을 처리하는 용도로 쓰세요 ゝ。∂)

  • 중요하다고 느낀 부분은 ⭐별⭐, 📌핀📌 이모티콘, 밑줄로 표시했고
  • 혼동될 만한 개념이나 용어들은 vs. 표시,
  • 명령어나 폴더/파일명은 하이라이트로 표시했으니

참고해 주세요!
또 노트정리 포스팅은 우주쓰레기가 다 정리되는 그 날까지 꾸준히 업로드할 예정이니 많은 관심 부탁드려용


상속과 프로퍼티 오버라이딩을 알아보기 전에..! Swift에서 말하는 '프로퍼티'가 다른 언어와 조금 달라 프로퍼티에 대해 먼저 간략히 설명합니다
상속의 정의에 대한 내용은 다루지 않은 점 참고해주세요!

📌 Property

  • ⭐ Swift에서는 프로퍼티가 저장, 연산 타입 크게 세 가지 프로퍼티 형태로 존재한다.

  • 프로퍼티의 종류

    • 인스턴스 저장 프로퍼티
    • 타입 저장 프로퍼티
    • 인스턴스 연산 프로퍼티
    • 타입 연산 프로퍼티
    • 지연 저장 프로퍼티
  • 프로퍼티는 구조체, 클래스, 열거형 내부에 구현할 수 있다. 다만 열거형 내부에는 연산 프로퍼티만 구현할 수 있다.

  • 연산 프로퍼티

    • 연산 프로퍼티는 직접 값을 갖지는 않고, 다른 저장 프로퍼티 의 값을 읽어 연산을 실행하거나 프로퍼티의 전달받은 값을 다른 프로퍼티에 저장하는 역할을 한다. 따라서 항상 var로 선언되며 저장 프로퍼티와 달리 저장 공간을 갖지 않는다.

      var name: Type {
          get {           // getter (다른 저장 연산프로퍼티의 값을 얻거나 연산하여 리턴할 때 사용)
              statements
              return expr
          }
          set(name) {     // setter (다른 저장프로퍼티에 값을 저장할 때 사용)
              statements
          }
      }

      Swift) 프로퍼티 정복하기 (2/4) - 연산 프로퍼티(Computed Property)

    • 연산 프로퍼티를 읽기전용으로는 구현할 수 있지만, 쓰기전용으로는 구현할 수 없다. 읽기전용으로 구현 하려면 get 블럭만 작성해주면 된다. 읽기전용은 get 블럭을 생략할 수 있다. 읽기, 쓰기 모두 가능하게 하려면 get 블럭과 set 블록을 모두 구현해주면 된다.

예) 프로퍼티 사용 예제

struct Student {
    // 1. 인스턴스 저장 프로퍼티
    var name: String = ""
    var `class`: String = "Swift"
    var koreanAge: Int = 0
    
    // 2. 인스턴스 연산 프로퍼티
    var westernAge: Int {
        get {
            return koreanAge - 1
        }
        set(inputValue) {
            koreanAge = inputValue + 1
        }
    }
    
    // 3. 타입 저장 프로퍼티
    static var typeDescription: String = "학생"
    
    /*
    // 인스턴스 메서드
    func selfIntroduce() {
        print("저는 \(self.class)반 \(name)입니다")
    }
     */
    
    // 읽기전용 인스턴스 연산 프로퍼티
    // 간단히 위의 selfIntroduce() 메서드를 대체할 수 있다
    var selfIntroduction: String {
        get {
            return "저는 \(self.class)\(name)입니다"
        }
    }
        
    /*
     // 타입 메서드
     static func selfIntroduce() {
     print("학생타입입니다")
     }
     */
    
    // 읽기전용 타입 연산 프로퍼티
    // 읽기전용에서는 get을 생략할 수 있다
    static var selfIntroduction: String {
        return "학생타입입니다"
    }
}
  • set 블록에서 암시적 매개변수 newValue를 사용할 수 있다.

📌 상속

  • 스위프트의 상속은 클래스, 프로토콜 등에서 가능하며 열거형, 구조체는 상속이 불가능하다.
  • 스위프트는 다중상속을 지원하지 않는다.

예제) 클래스 상속

// 기본 클래스(아무런 상속을 받지 않은 클래스) Person
class Person {
    var name: String = ""
    
    func selfIntroduce() {
        print("저는 \(name)입니다")
    }
    
    // final 키워드를 사용하여 재정의를 방지할 수 있다
    final func sayHello() {
        print("hello")
    }
    
    // 타입 메서드
    // 재정의 불가 타입 메서드 - static 키워드
    static func typeMethod() {
        print("type method - static")
    }
    
    // 재정의 가능 타입 메서드 - class 키워드
    class func classMethod() {
        print("type method - class")
    }
    
    // 재정의 가능한 class 메서드라도 final 키워드를 사용하면 재정의 할 수 없다
    // 메서드 앞의 `static`과 `final class`는 똑같은 역할을 한다
    final class func finalCalssMethod() {
        print("type method - final class")
    }
}

// Person을 상속받는 Student
class Student: Person {
		// 저장 프로퍼티
    var major: String = ""
    
    override func selfIntroduce() {
        print("저는 \(name)이고, 전공은 \(major)입니다")
    }
    
    override class func classMethod() {
        print("overriden type method - class")
    }
    
    // static을 사용한 타입 메서드는 재정의 할 수 없다
//    override static func typeMethod() {    }
    
    // final 키워드를 사용한 메서드, 프로퍼티는 재정의 할 수 없다
//    override func sayHello() {    }
//    override class func finalClassMethod() {    }

}
  • staticfinal class는 같은 역할을 수행한다.

📌 프로퍼티 오버라이딩

상속 받은 프로퍼티의 속성에 대한 (1) getter, setter를 제공하거나(즉 연산 속성을 추가) 값의 변경을 추적할 수 있는 (2) 프로퍼티 옵저버를 추가하는 정도로 알아두자.

  • ⭐⭐ 프로퍼티의 경우 ‘연산 속성’을 추가하는 건 가능하지만 상속 받은 저장 프로퍼티에 ‘저장 속성’을 추가하는 오버라이딩을 불가하다.
class Human { 
    var name = "Sodeul" // 저장 프로퍼티

		var alias: String { // 연산 프로퍼티가 getter로만 구현된 경우
        return self.name + " 바보"
    }
}
 
class Teacher: Human { 
		// 저장 속성을 추가하면 컴파일 에러 발생
    override var name: String = "Sodeul2"
}

class Teacher1: Human {
    var alias = "SodeulSodeul"
 
		// 연산 속성을 추가해주는 오버라이딩은 가능
    override var name: String {   
        get {
            return self.alias
        }
        set {
            self.alias = newValue
        }
    }
}

1) getter, setter 제공하기

  • 저장 프로퍼티의 오버라이딩
    • 서브클래스에서 getter만 구현하는 것은 안된다. 이유는 슈퍼클래스의 프로퍼티 name은 ‘저장 프로퍼티’이기에 기본적으로 읽기&쓰기가 모두 가능한 프로퍼티이기 때문이다.
  • 연산 프로퍼티의 오버라이딩
    • 1) 슈퍼 클래스의 연산 프로퍼티가 getter로만 구현된 경우 → 서브 클래스에서 getter만 구현하거나/setter를 추가 구현하는 오버라이딩 가능하다.
    • 2) 슈퍼 클래스의 연산 프로퍼티가 getter/setter 둘 다 구현된 경우 → 서브 클래스에서 getter만 구현하는 오버라이딩은 불가능하다.

2) 프로퍼티 옵저버 추가하기

  • 저장 프로퍼티의 경우, var로 선언된 프로퍼티만 오버라이딩으로 옵저버를 추가할 수 있다.
  • 연산 프로퍼티의 경우, getter/setter가 모두 구현된 경우만 오버라이딩으로 옵저버를 추가할 수 있다.
  • 원래 옵저버는 저장 프로퍼티에만 추가가 가능하지만, 연산 프로퍼티의 경우 오버라이딩 할 경우만 추가가 가능하다.

(추가) 프로퍼티 감시자

  • 프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때 원하는 동작을 수행할 수 있다. 값이 변경되기 직전에 willSet블럭이, 값이 변경된 직후에 didSet블럭이 호출된다. 둘 중 필요한 하나만 구현해 주어도 무관하다. 변경 되려는 값이 기존 값과 똑같더라도 프로퍼티 감시자는 항상 동작한다. willSet 블럭에서 암시적 매개변수 newValue를 사용할 수 있고, didSet 블럭에서 암시적 매개변수 oldValue를 사용할 수 있다.
var currencyRate: Double = 1100 {
        willSet(newRate) {
            print("환율이 \(currencyRate)에서 \(newRate)으로 변경될 예정입니다")
        }
        didSet(oldRate) {
            print("환율이 \(oldRate)에서 \(currencyRate)으로 변경되었습니다")
        }
}
  • 프로퍼티 감시자는 연산 프로퍼티에 사용할 수 없다.


Epilogue

본 포스팅은 야곰 님의 '스위프트 기본 문법' 강의를 토대로 작성되었습니다. 질문과 피드백은 언제나 환영입니다. 부족하거나 틀린 부분은 편하게 말씀해주시면 감사하겠습니다!

profile
마법학교 수석이 되고 싶은 평범한 대학생입니다 🪄

0개의 댓글