본문 바로가기

iOS/Swift

Swift) 상속(Inheritance) 정복하기 (2/2) - 오버라이딩(Overriding)

 

 

 

안녕하세요 :) 소들입니다 호홋

저번 상속의 기본 개념(기본 클래스, 서브 클래싱 등)에 대해 알았다면,

이번 포스팅에선 "오버라이딩"에 대해 공부할 거예요!!!!

 

오버라이딩.... 뭔가 엄청나게 많이 많이 들은 단어인데,

Swift에선 어떻게 오버라이딩을 하는지에 대해 알아 봅시다 :D

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

 

1. 오버라이딩(Overriding)이란?

 

서브 클래스는 슈퍼 클래스에서 상속할 인스턴스 메서드, 타입 메서드, 인스턴스 프로퍼티,

타입 프로퍼티, 서브스크립트를 구현할 수 있는데, 이것을 "오버라이딩(overriding)"이라 라고 한다

"override"라는 키워드를 정의 앞에 작성해야, override 키워드 없이 오버라이딩 할 경우 컴파일 에러가 난다

override 키워드가 있을 경우, 컴파일러는 슈퍼 클래스에 오버라이딩 선언한 것과 일치한 정의가 있는지 확인을 하고,

이것은 오버라이딩 정의가 올바른지 확인하는 것이다

 

역대급 읽기 싫은 정의네..

자, 오버라이딩이란 단어가 어색할 수 있지만,  이 단어는 한 마디로 "재정의"란 뜻임

재정의?? 이 카테고리가 상속에 있는 것 만큼 상속과 관련된 것이겠지!?!?

 

위 정의에 대해 다 이해해볼 것이니 겁 먹지 말고, 

메서드, 프로퍼티, 프로퍼티 옵저버 순으로 확인하며 공부해보겠음!!!

 

 

 

 

2. 메서드 오버라이딩(Method Overriding)

 

상속받은 인스턴스&타입 메서드를 오버라이딩(재정의) 하여,

하위 클래스 내에서 해당 메서드를 원하는 대로 구현을 할 수 있다

 

 

class Human {
    func description() {
        print("나는 사람입니다")
    }
}
 
class Teacher: Human {
}

 

 

이전 포스팅에서 봤듯이, Human과 Teacher이란 클래스로 보겟음

 Human이란 클래스엔 description이란 메서드가 있고,

Teacher이란 클래스는 Human 클래스를 상속 받으며 아무 것도 구현하지 않음

 

 

let sodeulTeacher = .init()
sodeul.description()

 

 

하지만 우린 ㅣㅇ렇게 Teacher 클래스 인스턴스로

description이란 메서드에 접근할 수 있단 걸 이제 알음!!

왜냐?? Teacher이란 클래스가 Human이란 클래스를 상속받기 때문에,

따라서 Human이란 멤버들에 모두 접근 가능하기 때문!!!!

 

따라서 다음과 같이 

 

 

 

 

Human에서 구현한 메서드가 제대로 실행 됨

근데 있잖음 Teacher이란 클래스가 까탈스러워서 description이란 메서드르 실행하면

"나는 선생입니다" 라는 출력을 하고 싶음..

 

그럼 어떻게 할까?? 

만약 description이란 메서드를 Teacher이란 클래스에서 직접 만들면

 

 

class TeacherHuman {
    func description() {        // Overriding declaration requires an 'override' keyword, Insert 'override '
        print("나는 선생입니다")
    }
}

 

 

선언을 재정의 하려면 "override"란 키워드를 써야 한다며 에러가 남

왜 에러가 나냐면, 이미 상속받는 클래스인 Human 클래스에 동일한 메서드가 있기 때문

에러가 나는 것임... 한 클래스 내에서 완벽하게 동일한 메서드를 두개 선언할 수는 없으니

 

따라서, 이때 사용하는 것이 오버라이딩, 즉 "재정의" 라는 것

내 슈퍼클래스인 Human의 description 함수를 내 입맛에 맞춰서 재정의해서 쓰겠다!

하고 선언하는 것이 override라는 키워드인 것임

 

방법은 간단함 오버라이딩 하고 싶은 메서드 앞에 override란 키워드만 붙여주면 됨!

 

 

class TeacherHuman {
    override func description() {
        print("나는 선생입니다")
    }
}

 

 

이렇게!!!! 그럼 이제 Teacher이란 클래스는 Human이란 슈퍼클래스의

description이란 메서드를 오버라이딩(재정의) 하는 것임!!

 

재정의를 한 만큼,

 

 

let sodeulTeacher = .init()
sodeul.description()

 

 

이제 다시 Teacher이란 클래스 인스턴스에 대고 description을 실행하면

 

 

 

 

더이상 Super Class인 Human의 description이 실행되지 않고

이렇게 내가 오버라이딩(재정의) 한 메서드가 실행됨!!!!

 

와 오버라이딩이란 슈퍼 클래스의 메서드를 재정의 해서 사용할 수 있꾸나를 이제 알았음!!

근데 만약 Teacher이란 클래스가

 

나는.... 말이야..

"나는 사람입니다"도 출력하고 싶고,,

"나는 선생입니다"도 출력하고 싶어..

 

한다면 어떻게 해야할까?

오버라이딩을 할 경우, 이미 Super Class의 메서드를 재정의 해버린 것이기 때문에

더이상 슈퍼 클래스의 description이란 메서드는 실행되지 않음

 

그럼 어째? 어쩌긴 뭘 어째!!

서브 클래스에서 오버라이딩 할 때 슈퍼 클래스의 description을 직접 실행시켜줌 되지!!

 

 

class TeacherHuman {
    override func description() {
        super.description()
        print("나는 선생입니다")
    }
}

 

 

이렇게 super를 이용하면, 슈퍼 클래스에 접근할 수 있기 때문에

super를 이용해서 Human 클래스의 description을 실행시켜 주는 것임!!

 

그러면, 

 

 

 

 

이렇게 슈퍼 클래스인 Human Class의 description도,

Teacher Class의 오버라이딩한 description도 모두 실행시킬 수 있음!!!

 

 

주의할 점?은, 당연히 sync한 환경에서 코드의 실행 순서는 스택(LIFO)이기 때문에,

 

 

class TeacherHuman {
    override func description() {
        print("나는 선생입니다")
        super.description()
    }
}

 

 

이렇게 super를 마지막에 불러주면

 

 

 

 

당연히 마지막에 실행 되겠...

이 말을 하는 이유가, 슈퍼 클래스의 메서드를 부르는 작업을 꼭 맨처음에 해야 하는 것은 아니란 것..

 

 

그리고 위에서 override란 키워드를 붙이면 "컴파일러"는 해당 정의가

슈퍼 클래스에 있는지 확인하는 작업을 하여, 오버라이딩이 맞는지 아닌지 확인한다 했잖음??

 

만약 내가, Human 클래스에 존재하지 않는 printHello란 메서드에 override란 키워드를 붙이면,

 

 

class TeacherHuman {
    override func printHello() { }       // Method does not override any method from its superclass
}
 

 

 

컴파일러는 슈퍼 클래스에서 printHello와 동일한 메서드가 있는지 확인하는 작업을 하는데,

만약 동일한 메서드가 없을 경우 

printHello란 메서드는 슈퍼 클래스의 메서드를 오버라이딩 하는 게 아니야!!!!

라며 컴파일러가 에러를 뱉는단 말임 :)

 

이것이 메서드 오버라이딩!!! 

인스턴스 메서드로 예제를 봤지만, 타입 메서드 또한 같음!!!

 

 

 

 

3. 프로퍼티 오버라이딩(Property Overriding)

 

상속받은 프로퍼티를 오버라이딩(재정의)하여 해당 속성에 대한 getter, setter를 제공하거나

상속받은 프로퍼티 값의 변경을 추적할 수 있도록 프로퍼티 옵저버를 추가할 수 있다

 

뭔가 프로퍼티를 오버라이딩 한다는 것은

getter, setter를 추가할 수 있다, 즉 "연산 속성"을 추가할 수 있단 것이고,

프로퍼티 옵저버를 추가할 수 있다는 것 같음!!!!

하나씩 보자 :)

 

 

 

 3-1. 연산 속성 추가하기 

 

서브 클래스에서는 상속된 프로퍼티의 특성이 저장 프로퍼티인지, 연산프로퍼티인지 알 수 없으며

상속받은 프로퍼티의 이름과 타입 정도만 알고 있다

때문에 오버라이딩(재정의) 할 경우 컴파일러는 슈퍼 클래스에 해당 이름과 "타입"을 가진 프로퍼티가 있는지

확인해야 하기 때문에 오버라이딩(재정의) 시 "프로퍼티의 이름"과 "타입"을 반드시 명시해야 한다

 

뭐... 긴 내용이지만,

프로퍼티를 오버라이딩 할 거면 "프로퍼티의 이름"과 "타입"을 반드시 명시해라!!!

이정도 쯤인 것 같음.. 예제로 보자 :)

 

 

 ① 저장 프로퍼티 

 

먼저, 프로퍼티의 경우 "연산" 속성을 추가하는 것 정도만 가능하다고 했음

그럼 청개구리 같지만 상속받은 저장 프로퍼티에 "저장" 속성을 추가하는 오버라이딩은 안 될까!?

 

 

class Human {
    var name = "Sodeul"
}
 
class TeacherHuman {
    override var name: String = "Sodeul2"       // Cannot override with a stored property 'name'
}

 

 

네! 안 된다면 안 됩니다!! 

저장 프로퍼티 name을 오버라이딩 할 수 없다구 함!!!

(따라서 Teacher이란 클래스에선 name이란 저장 프로퍼티는 선언하지 못함)

 

그럼 상속받은 저장 프로퍼티 name에 "연산" 속성인 get, set을 추가하는 오버라이딩은?!?

 

 

class TeacherHuman {
    var alias = "SodeulSodeul"
 
    override var name: String {    
        return self.alias
    }
}
 

 

 

처음에 엥 안되나? 했는데, 에러 메세지를 보면

변경 가능한 프로퍼티를 read-only 프로퍼티로 오버라이딩 할 수 없다고 함

 

무슨 말이냐면, 이미 슈퍼 크래스인 Human 클래스에서 name이란 프로퍼티

"저장 프로퍼티"여서 기본적으로 getter / setter가 모두 제공되는 읽기&쓰기가 가능한 프로퍼티

 

근데 Teacher이란 서브 클래스에서 name이란 프로퍼티를 getter만 가능 하게끔 오버라이딩 해버리니까,

야 얘는 읽기/쓰기 모두 가능한 앤데 왜 서브 클래스 너가 읽기만 가능하게 제한을 둬!! 하고 에러가 나는 것임

 

따라서, 만약 저장 프로퍼티를 오버라이딩 하고 싶다면,

 

 

class TeacherHuman {
    var alias = "SodeulSodeul"
 
    override var name: String {   
        get {
            return self.alias
        }
        set {
            self.alias = newValue
        }
    }
}
 

 

 

이렇게 getter / setter를 모두 구현해주면 오버라이딩이 됨!!!

만약 오버라이딩이 제대로 됐다면, Teacher이란 클래스 인스턴스의 name에 접근 했을 경우

슈퍼 클래스에 정의된 "Sodeul"이 아니라, 오버라이딩 된 alias의 값인 "SodeulSodeul"이 나와야 하잖음?!

 

 

 

 

쨔쟌 :)

 

 

 ② 연산 프로퍼티 

 

위에서 저장 프로퍼티의 경우 getter / setter가 이미 구현 되어 있는데

오버라이딩 해서 getter만 구현하는 것은 안 된다고 했잖음??

 

연산 프로퍼티 또한 마찬가지임!!

 

class Human {
    var name = "Sodeul"
 
    var alias: String {
        return self.name + " 바보"
    }
}

 

 

이렇게 Human이란 클래스에 alias라는 연산 프로퍼티가 getter로만 구현된 경우, 

 

 

class TeacherHuman {
    override var alias: String {
        return self.name + " 멍청이"
    }
}
 

 

class TeacherHuman {
    override var alias: String {
        get {
            return self.name + " 멍청이"
        }
        set {
            self.name = newValue
        }
    }
}
 

 

 

서브 클래스위서 위같이 getter만 구현하거나 setter 를 추가 구하는 오버라이은 가능하지만,

 

 

class Human {
    var name = "Sodeul"
 
    var alias: String {
        get {
            return self.name + " 바보"
        }
        set {
            self.name = newValue
        }
    }
}
 

 

 

이렇게 Human이란 클래스에 alias라는 연산 프로퍼티가 getter / setter로 구현된 경우, 

 

 

class TeacherHuman {
    override var alias: String {        // Cannot override mutable property with read-only property 'alias'
       return self.name + " 멍청이"
    }
}

 

 

서브 클래스위서 위같이 getter만 구현하는 오버라이딩은 안됨!!

 

 

확인해보면, Human의 alias와 Teacher의 오버라이딩 된 alias를 접근할 때

 

 

 

 

Human 인스턴스의 경우 원본 getter가,

Teacher 인스턴스의 경우 오버라이딩 된 getter가 불리는 것을 볼 수 있음 :)

 

 

 

3-2. 프로퍼티 옵저버 추가하기

 

저장 프로퍼티의 경우, var로 선언된 프로퍼티만 오버라이딩으로 옵저버를 추가할 수 있다

연산 프로퍼티의 경우, getter / setter가 모두 구현된 경우만 오버라이딩으로 옵저버를 추가할 수 있다

 

당연한 내용인데 이렇게 정의로 써놓으면 참~~ 어렵단 말이징.. /_\

먼저, 프로퍼티 옵저버 포스팅에서 공부 했지만 프로퍼티 옵저버는 

 

- 저장 프로퍼티에만 추가가 가능하다

- 연산 프로퍼티의 경우, 서브 클래스에서 부모 클래스의 연산 프로퍼티를 오버라이딩 할 경우만 추가가 가능하다

 

이렇게 두 가지 가능 하댔음!!!!

근데 우린 지금 오버라이딩을 공부하잖음???? 따라서 저장&연산 프로퍼티 둘 다 가능한 것임

 

 

 ① 저장 프로퍼티 

 

먼저, var로 선언된 경우만 가능하다 했음

왜냐????? 프로퍼티 옵저버의 경우 "값이 변경"될 때를 알려주는 것인데

상수면 값이 바뀔 일 없는데 왜 씀......??? 따라서 변수일 때만 가능한 아주 당연한 것임

 

 

class Human {
    var name = "Sodeul"
}
 
class TeacherHuman {
    override var name: String {
        willSet {
            print("name 값 변경 된다!!! \(newValue)")
        }
        didSet {
            print("name 값 변경 됐다!!! \(oldValue)")
        }
    }
}

 

 

이렇게 슈퍼 클래스의 name이란 "변수 저장 프로퍼티"를,

Teacher이란 서브 클래스에서 오버라이딩 하여 프로퍼티 옵저버를 추가할 수 있음

 

 

let teacherTeacher = .init()
teacher.name = "Unknown"

 

 

당연히 오버라이딩 해서 추가한 프로퍼티 옵저버도 제대로

 

 

 

 

작동함!!!!!!!!

 

+

근데, 한 가지 유의할 것은 맨 처음 정의에서 말 했듯이,

컴파일러는 이 alias가 오버라이딩이 되는 게 맞는지 확인하기 위해서

프로퍼티를 오버라이딩 시엔 "프로퍼티 이름", "타입"이 반드시 명시가 되어 있어야 한다 했음!!

 

따라서 만약 슈퍼 클래스의 name이 타입 추론에 의해 타입이 명시 되어 있지 않다고 해도,

프로퍼티인 name을 오버라이딩 할 경우엔 타입을 반드시 명시해 주어야 함

 

만약 안 할 경우 

 

 

 

 

타입 명시하라면서 에러가 나용 :)

 

 

 ② 연산 프로퍼티 

 

getter / setter가 모두 구현된 연산 프로퍼티만 프로퍼티 옵저버를 추가할 수 있댔잖음?

이또한 당연한거... getter만 구현된 경우 setter(값 변경)이 호출될 일이 없는데 왜 옵저버를 붙임?? 이말임

 

따라서 getter / setter 가 모두 붙어있는 프로퍼티만 오버라이딩해서 프로퍼티 옵저버를 추가할 수 있음

 

 

class Human {
    var name = "Sodeul"
 
    var alias: String {
        get {
            return name + " 바보"
        }
        set {
            self.name = newValue
        }
    }
}
 
class Teacher: Human {
    override var alias: String {
        willSet {
            print("연산 프로퍼티 set이 실행되려고 한다!!!!")
        }
        didSet {
            print("연산 프로퍼티 set이 실행 되었다!!!!")
        }
    }
}
 

 

 

이렇게 추가 가능하고, 테스트 해보면

 

 

let teacherTeacher = .init()
teacher.alias = "Unknown"

 

 

당연히 정상적으로

 

 

 

 

작동 합니다 :))))))))

 

 

 

 

4. final :: 오버라이딩을 금지한다!!

 

이전 포스팅에서 class 앞에 final이란 키워드를 붙이면

해당 클래스의 상속을 금지하는 것이라구 했잖음!?!?

 

오버라이딩 또한 마찬가지임!!! 

오버라이딩이 가능한 프로퍼티, 메서드, 서브스크립트 등에 final을 붙이면,

해당 정의는 더이상 "오버라이딩이 불가"해짐

 

 

class Human {
    final var name = "Sodeul"
    final func description() {
        print("나는 사람입니다")
    }
}

 

 

이렇게 정의 앞에 final을 붙일 경우,

 

 

 

 

더이상 서브 클래스에서 해당 정의를 오버라이딩 할 수 없음

 

 

🌸 주의할 것 🌸

final을 붙인다고 오버라이딩이 금지된 것이지,

서브 클래스에서 접근 자체가 금지된 것은 아님!!!!!

 

 

let teacherTeacher = .init()
teacher.name
teacher.description()

 

 

따라서 final이 붙은 멤버에도 서브 클래스에서 접근은 가능함 :)

 

 

 

 

 

 

 

.

.

.

왜이렇게 힘든가 했더니.. 포스팅이 정말 길군........

오늘은 오버라이딩에 대해 정말 정복하려고 노력을....... 많이.......

제가 정답이 아니라 피드백은 언제든 환영이고,

궁금한 점이 있어도 언제든 댓글 주세요 :)

 

 

 



Calendar
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
최근 댓글
Visits
Today
Yesterday