Swift 문법 - 상속 (Inheritance)

eelijus·2022년 11월 28일
0

Swift Syntax

목록 보기
4/11

💡상속

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html

상속(Inheritance)

Swift의 상속은 클래스. 프로토콜 등에서 가능. 열거형, 구조체에서는 상속이 불가능하다.
다중 상속은 불가능.

상속이란?

자식클래스는 부모클래스로부터 상속받은 프로퍼티에 접근하고, 메서드를 호풀할 수 있다. 서브스크립트 사용도 가능.

부모클래스로부터 상속받은 프로퍼티, 메서드, 서브스크립트 등을 자신만의 내용으로 재정의(override) 가능.

자식클래스가 부모클래스의 요소를 재정의 한다는 것을 명확히 해야함.

상속받은 프로퍼티의 값이 변경되었을때 알려주는 프로퍼티 옵저버 구현 가능.

연산 프로퍼티를 정의한 클래스에서는 연산 프로퍼티 옵저버 사용 불가능, 허나 부모로부터 저장 혹은 연산 프로퍼티를 상속받은 자식클래스에서는 프로퍼티 옵저버를 구현할 수 있다.

키워드

class : class 키워드로 생성한 타입 메서드는 재정의가 가능

static : static 키워드로 생성한 타입 메서드는 재정의 불가능

final : 프로퍼티 혹은 메서드 앞에 붙여 재정의 방지

override: override 키워드로 부모 클래스의 메소드 재정의 가능

staic == final class

  • super 프로퍼티

자식 클래스에서 부모 클래스의 특성을 재정의 했을 때, 부모 클래스 버전의 특성을 그대로 사용하고 싶으면 super 프로퍼티를 사용하면 된다.

타입 내에서 사용 : 부모 클래스의 타입 프로퍼티 및 타입 메서드에 접근 가능

인스턴스 내에서 사용 : 부모 클래스의 인스턴스 프로퍼티, 인스턴스 메서드, 인스턴스 서브스크립트에 접근 가능

super.property
super.method()
super[index] - 부모 클래스 버전의 서브 스크립트에 접근

용어

기반 클래스 (Base class) : 다른 클래스로부터 상속을 받지 않은 클래스

부모 클래스 (Superclass / Parent class)

자식 클래스 (Subclass / Child class)

기본 예시

class Person {
    var name: String = ""
    
    func selfIntroduce() {
        print("나는 \(name)이다!")
    }
    
    //final 키워드를 사용하여 재정의를 방지할 수 있다.
    final func sayHello() {
        print("helo~")
    }

    //타입 메서드
    //재정의 불가 타입 메서드 - static
    static func typeMethod() {
        print("type method - static")
    }
    
    //재정의 가능 타입 메서드 - class
    class func classMethod() {
        print("type method - class")
    }
    
    //class 메서드라도 final 키워드가 붙으면 재정의 불가능
    //static 과 final class는 결국 같은 역할이다.
    final class func finalClassMethod() {
        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() {
        print("override")
    }
    
    //final로 선언한 프로퍼티, 메서드는 재정의 불가
    //컴파일 오류
    override func sayHello() {
        print("override")
    }
    override class func finalClassMethod() {
        print("override")
    }
}
  • 클래스의 상속과 재정의 사용 예시
class Person {
    var name: String = ""
    
    func selfIntroduce() {
        print("나는 \(name)이다!")
    }
    
    //final 키워드를 사용하여 재정의를 방지할 수 있다.
    final func sayHello() {
        print("helo~")
    }

    //타입 메서드
    //재정의 불가 타입 메서드 - static
    static func typeMethod() {
        print("type method - static")
    }
    
    //재정의 가능 타입 메서드 - class
    class func classMethod() {
        print("type method - class")
    }
    
    //class 메서드라도 final 키워드가 붙으면 재정의 불가능
    //static 과 final class는 결국 같은 역할이다.
    final class func finalClassMethod() {
        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() {
//        print("override")
//    }
    
    //final로 선언한 프로퍼티, 메서드는 재정의 불가
    //컴파일 오류
//    override func sayHello() {
//        print("override")
//    }
//    override class func finalClassMethod() {
//        print("override")
//    }
}

let sujilee: Person = Person()
let jji: Student = Student()

sujilee.name = "sujilee"
jji.name = "jji"
jji.major = "backend"

Person.classMethod()
Person.typeMethod()
Person.finalClassMethod()

Student.classMethod()
Student.typeMethod()
Student.finalClassMethod()

sujilee.selfIntroduce()
jji.selfIntroduce()
type method - class
type method - static
type method - final class
overriden type method - class
type method - static
type method - final class
나는 sujilee이다!
나는 jji이고, 전공은 backend이다!

재정의 (Override)

자식클래스는 부모클래스로부터 상속받은 타입 프로퍼티, 메서드, 인스턴스, 서브스크립트 등을 그대로 사용하지 않고 자신만의 기능으로 변경하여 사용할 수 있다.

컴파일러가 슈퍼클래스(부모 포함 상위 부모 클래스)에 해당 특성이 있는지 확인 후 재정의. 해당 특성이 없다면 컴파일 오류 발생

프로퍼티 재정의

자식클래스에서 상속받은 인스턴스 프로퍼티 및 타입 프로퍼티를 재정의 할 수 있다. 이때 재정의란, 프로퍼티 그 자체가 아닌 프로퍼티 접근자(getter), 설정자(setter), 옵저버 등의 재정의를 일컫는다.

그러므로 프로퍼티 재정의 시, 저장 프로퍼티로는 재정의 할 수 없다.

슈퍼클래스의 저장 프로퍼티 및 연산 프로퍼티를 접근자(getter), 제어자(setter)로 재정의가능.

자식클래스에서는 상속받은 프로퍼티 종류(저장, 연산 등)는 알지 못하고, 해당 프로퍼티의 타입만 알 수 있다.

슈퍼 클래스에서 프로퍼티가 읽기 전용이었어도 자식 클래스에서 해당 프로퍼티를 읽기, 쓰기가 가능하게 재정의할 수 있다.

접근자(getter)에 기능 변경이 필요없다면 super 키워드를 사용해 부모 클래스의 접근자(getter)를 사용해 값을 받아 반환하면 된다.

  • 프로퍼티 접근자(getter), 설정자(setter) 사용 예시
class Number {
    //get, set을 사용하기 위해서는 연산된 결과값을 저장할 변수가 반드시 필요하다.
    var returnValue: Int = 0
    
    // 값을 연산해 반환하거나 결과값에 할당하는 역할을 한다.
    var carriageValue: Int {
        get {
            return returnValue
        }
      //시스템에서 기본적으로 newValue라는 상수명 제공
      //암시적 매개변수 newValue 사용가능
        set(inputValue) {
            returnValue = inputValue * 2
        }
    }
}

var doubledNumberInstance: Number = Number()

doubledNumberInstance.carriageValue = 42
print(doubledNumberInstance.returnValue)
//OUTPUT : 84
  • 프로퍼티 재정의 사용예시
class Person {
    //저장 프로퍼티
    var name: String = ""
    var age: Int = 0
    //연산 프로퍼티
    var nextAge: Int {
        return self.age + 1
    }
    var introduction: String {
        return "이름 : \(name). 나이(만) : \(age)"
    }
}
 
class Student: Person {
    var grade: String = "B"
    //부모클래스의 연산 프로퍼티 재정의
    override var introduction: String {
        return super.introduction + " " + "성적 : \(grade)"
    }
    //부모클래스 읽기전용 이였던 연산 프로퍼티를 읽기/쓰기 모두 가능하도록 재정의
    override var nextAge: Int {
        get {
            return super.nextAge
        }
      //nextAge에 값을 새로 할당하면, 해당 인스턴스의 age의 값 또한 아래와 같이 새로 할당된다.
        set {
            self.age = newValue - 1
        }
    }
}

let sujilee: Person = Person()
sujilee.name = "sujilee"
sujilee.age = 29
//기존 age에 1을 더하는 연산 프로퍼티
print(sujilee.nextAge) //30

let jji: Student = Student()
jji.name = "jji"
jji.age = 28
//get. 기존 age에 1 더함
print(jji.nextAge) //29
print(jji.age) //28
//set. 인스턴스의 age는 이 값에서 -1 된 값이 할당됨
jji.nextAge = 40
print(jji.nextAge) //40
print(jji.age) //39

메서드 재정의

  • 메서드 재정의 사용 에시
class Person {
    
    //저장 프로퍼티
    var name: String = ""
    var age: Int = 0
    
    //연산 프로퍼티
    var introduction: String {
        return "이름 : \(name), 나이 : \(age)"
    }
    
    //인스턴스 메서드
    func speech() {
        print("나는 이수지다!")
    }
    
    //타입 메서드 - class
    //static 키워드가 아닌 class 키워드 사용. 클래스에서 정의해서..?
    class func comment() -> String {
        return "상어왕이 될거야"
    }
}

class Student: Person {
    
    //Swift는 메서드의 매개변수나 반환 타입이 다르면 함수명이 같아도 별개의 메서드로 취급한다.
    //super 키워드로 부모 클래스 버전의 comment 메서드 호출
    class func comment() {
        print(super.comment())
    }
    
    //부모 클래스에서 class 키워드를 사용해 정의한 타입 메서드이기 때문에 자식 클래스에서 구현부를 재정의 할 수 있다.
    override class func comment() -> String {
        return "재정의한 comment() 함수"
    }
    
    override func speech() {
        print("나는 학생 \(name) 입니다.")
    }
}

//부모 클래스 인스턴스
let sujilee: Person = Person()
sujilee.speech()
print(Person.comment())

//자식 클래스 인스턴스
let jji: Student = Student()
jji.speech()
//아래와 같이 함수를 호출하면, Ambiguous use라는 경고 메세지가 뜬다. 이유는 같은 함수명의 메서드가 존재하기 때문.
//타입 캐스팅 연산자 as를 사용해 반환타입을 명시해주자.
Student.comment() as Void
print(Student.comment() as String)

프로퍼티 옵저버 재정의

프로퍼티 옵저버도 접근자(getter), 설정자(setter)처럼 재정의가 가능하다.

슈퍼 클래스에서 정의된 프로퍼티가 연산인지 저장인지는 상관없음.

상수 저장 프로퍼티나 읽기 전용(getter) 연산 프로퍼티는 재정의 불가.

원칙적으로 상수 저장 프로퍼티 및 읽기 전용 연산 프로퍼티는 willSet / didSet메서드를 사용한 프로퍼티 옵저버를 사용할 수 없음.

자식 클래스에서 재정의된 프로퍼티 옵저버가 동작할 때, 슈퍼 클래스의 해당 프로퍼티 옵저버 또한 동시에 동작한다.

프로퍼티의 접근자(getter)와 옵저버는 동시에 재정의 할 수 없다. 원한다면 재정의하는 접근자(getter)에 프로퍼티 옵저버의 역할을 구현해야힘.

  • 프로퍼티 옵저버 재정의 예시
class Person {
    var name: String = ""
    //저장 프로퍼티 옵저버
    var age: Int = 0 {
        didSet {
            print("\(self.name)의 나이 : \(self.age)")
        }
    }
    var koreanAge: Int {
        return self.age + 1
    }
    
    var fullName : String {
        get {
            return self.name
        }
        set {
            self.name = newValue
        }
    }
}

class Student: Person {
    //부모 클래스의 저장 프로퍼티의 옵저버 재정의
    override var age: Int {
        didSet {
            print("학생 \(self.name)의 나이 \(self.age)")
        }
    }
    //koreanAge 및 fullName 연산 프로퍼티의 옵저버 정의
    override var koreanAge: Int {
        get {
            return super.koreanAge
        }
        set {
            self.age = newValue - 1
        }
        //접근자(getter)와 옵저버 동시 재정의 불가
        //didSet { }
    }
    
    override var fullName: String {
        didSet {
            print("Full name: \(self.fullName)")
        }
    }
}


let sujilee: Person = Person()
sujilee.name = "sujilee"
//Person의 age 프로퍼티 옵저버 동작
sujilee.age = 27
//컴파일 오류 : Person 클래스의 koreanAge는 read-only.
//sujilee.koreanAge = 29
sujilee.fullName = "sujileelea"
print(sujilee.koreanAge)

let eelijus: Student = Student()
eelijus.name = "eelijus"
//Person의 age 프로퍼티 옵저버 동작
//Student의 age 프로퍼티 옵저버 동작
eelijus.age = 27
//koreanAge의 setter가 age에 새 값을 할당하므로,Person의 age 옵저버와 Student의 age 옵저버가 모두 동작
eelijus.koreanAge = 29
//Student의 fullName 프로퍼티 옵저버 동작
eelijus.fullName = "eelijused"
print(eelijus.age)
print(eelijus.koreanAge)
sujilee의 나이 : 27
28
eelijus의 나이 : 27
학생 eelijus의 나이 27
eelijus의 나이 : 28
학생 eelijus의 나이 28
Full name: eelijused
28
29

재정의 방지

final 키워드를 사용해 자식 클래스에서의 재정의를 재한할 수 있다. 이를 무시하고 부모 클래스에서final 키워드로 선언한 요소를 자식 클래스에서 재정의하려고 할 시, 컴파일 오류가 발생한다.

클래스 자체를 상속 불가 클래스로 만드려면 class 앞에 final 키워드를 사용하면 된다.

상속이 방지된 클래스를 상속 받으려고 시도하면 컴파일 오류 발생.

  • 재정의 방지 예시
class ParentClass {
    final var finalValue: String = ""
    
    final func finalFunc() {
        print("print finalFunc")
    }
}

final class FinalClass: ParentClass {
    //ParentClass의 프로퍼티 및 메서드는 재정의 방지가 되어있기 때문에 자식 클래스에서 재정의 불가능
    //컴파일 오류
    override var finalValue: String {
        get {
            return "overriden finalValue's getter"
        }
        set {
            super.finalValue = newValue
        }
    }
    
    //컴파일 오류
    override func finalFunc() {
        print("overriden final func")
    }
}

//컴파일 오류
class SubClass: FinalClass {
    var value: String = ""
}

나머지 공부

위 이미지에 대한 것은 추후에 보충 공부합시다!







참고 링크

https://yagom.github.io/swift_basic/contents/14_inheritance/

https://seons-dev.tistory.com/entry/Swift-%EA%B8%B0%EC%B4%88-%EB%AC%B8%EB%B2%95-%EB%AA%A8%EC%9D%8C#%EC%83%81%EC%86%8D

profile
sujileelea

0개의 댓글