티스토리 뷰
안녕하세요 :) Zedd입니다.
오늘은 Inheritance!! 상속이죠. 상속은 정말 많이 썼지만..정리를 해볼려고 합니당
시작할게요!
Inheritance(상속)
Inheritance. 이제부터 상속이라고 언급할게요.
Swift에서 클래스, 구조체, 열거형중에 상속을 받을 수 있는건?????
네! 클래스 밖에 없죠 :) 그것도 단 하나의 클래스만 상속받을 수 있어요 XD
(프르토콜은 여러개를 "채택"할 수 있었지만요!)
아무튼 이 사실을 알고 읽으면 좋으실것 같아요.
클래스는 메소드, 프로터티 및 다른 특성(characteristics)을 다른 클래스에서 상속받을 수 있습니다.
한 클래스가 다른 클래스를 상속할 때, 상속하는 클래스는 하위클래스(subclass)로, 상속받은 클래스는 슈퍼클래스(superclass)로 알려져있습니다.
상속은 Swift에서 클래스를 다른 타입과 차별화 하는 기본 동작입니다.
Swift의 클래스는 슈퍼클래스에 속한 메소드, 프로퍼티 및 하위 스크르립트를 호출하고 접근 할 수 있으며, 해당 메소드, 속성 및 하위 스크립트의 재정의(override)한 버전을 제공하여, 해당 동작을 수정하거나 미세 조정할 수 있습니다.
Swift는 override 정의에 일치하는 슈퍼클래스 정의가 있는지 확인하여 override가 올바른지 확인하는데 도움이 됩니다.
또한 클래스는 프로퍼티 옵저버(<프로퍼티 옵저버>글 참고)를 상속된 프로퍼티에 추가하여 프로퍼티 값이 변경될 때 알림을 받을 수 있습니다.
프로퍼티 옵저버는 원래 저장 또는 연산 프로퍼티로 정의되었는지 여부에 관계없이 모든 프로퍼티에 추가할 수 있습니다. (밑에서 설명)
Defining a Base Class
다른 클래스를 상속받지 않는 클래스는 "기본 클래스(base class)"라고 합니다.
Swift 클래스는 보편적인 기본 클래스에서 상속하지 않습니다. 슈퍼클래스를 지정하지 않고 정의하는 클래스는 자동으로 기본클래스가 되어, extension(확장) 할 수 있습니다.
class Vehicle {
}
자 Vehicle이라는 클래스는 아무것도 상속받지 않죠? 이러면 Vehicle은 기본클래스가 된다는 소리입니다.
프로퍼티와 메소드를 추가해볼게요.
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// do nothing
}
}
탈것에는 현재속도와 설명이 있나보네요. 현재속도는 가변 저장프로퍼티로 선언이 되었고, 설명은 연산프로퍼티로 선언이 되었네요.
그리고 makeNoise라는 메소드! 이 탈것이 소음을 내는지?를 보는 그런 메소드 같네요.
이 클래스 프로퍼티에는 모두 기본값이 있어서, 이니셜라이저를 따로 안만들어도 되겠죠?
그럼 이제 Vehicle라는 이름을 가진 하나의 "타입"이 정의가 된것이니 Vehicle타입의 인스턴스를 만들어봅시다.
let someVehicle = Vehicle()
someVehicle이라는 Vehicle클래스의 인스턴스를 만들어주었습니다!!
그럼 이제 Vehicle의 프로퍼티와 메소드에 접근이 가능해지죠.
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour
Subclassing
class SomeSubclass: SomeSuperclass {
// subclass definition goes here
}
이렇게요!
SomeSubclass는 하위클래스고, SomeSuperclass는 슈퍼클래스이죠.
:으로 구분된 것. 보이시죠?
그럼 우리는 위에서 Vehicle라는 클래스를 만들었으니까..이 Vehicle을 상속하는 서브클래스를 하나 만들어봅시다. "탈것"에는 여러가지가 있겠죠? 자전거도 하나의 탈것이니까 자전거 클래스를 하나 만들어보겠습니다.
class Bicycle: Vehicle {
var hasBasket = false
}
Bicycle이라는 클래스에서, Vehicle라는 클래스를 상속했으니, Vehicle은 슈퍼클래스가 되겠고,
Bicycle은 하위클래스가 되겠네요.
이 Bicycle클래스는 Vehicle의 프로퍼티와 메소드들을 상속할 수 있을 뿐만 아니라, 새로운 특성까지도 추가할 수 있다고 그랬죠? Bicycle 클래스는 currentSpeed 및 description 속성과 makeNoise()메소드와 같이 Vehicle의 모든 특성을 자동으로 얻습니다.
상속받은 특성 이외에도, 우리는 hasBasket이라는 저장프로퍼티 하나를 추가했습니다. 기본값을 줬으니 역시 Bicycle클래스에도 이니셜라이저는 필요가 없겠네요.
기본적으로 새로 만드는 모든~~~ Bicycle인스턴스에는 바구니(Basket)이 그럼 없겠네요. false로 지정이 되어 있으니까요. 하지만 우리는 이걸 true로 설정할 수 도 있어요.
let bicycle = Bicycle()
bicycle.hasBasket = true
이렇게요.
bicycle은 방금 만든 Bicycle클래스의 인스턴스였는데.. Bicycle클래스는 Vehicle클래스를 상속했으니 Vehicle클래스의 특성에 모두 접근이 가능하죠?
접근해보겠습니다.
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
자...우리는 분명 Bicycle클래스 인스턴스를 만들었는데, 그 인스턴스로 currentSpeed, description같은 Vehicle클래스에 있는 프로퍼티들에 접근이 가능해졌네요. 이게 다 뭐때문?
상속때문 ㅇㅇ
자...그럼 상속을 하나 더 해봅시다.
아ㅇㅇ 자전거는 탈것이야..근데 내가 2인승 자전거도 역시 "탈것"이죠? 근데 "자전거"이기도 하죠?
이 Bicycle클래스를 상속하는 2인승 자전거(tandem)이라는 클래스를 하나 만들어볼게요.
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
자.. Bicycle클래스를 상속하는 Tandem클래스를 하나 만들었어요.
어..방금 Bicycle클래스는 Vehicle클래스를 상속했는데...이게 가능한가요?
넵 가능합니다 :)
그럼..이제 뭔가 예상이 가죠.
Tandem클래스는 일단 Bicycle클래스를 상속했으니 Bicycle클래스가 가지고 있는 특성에 모두 접근 할 수 있어요. Bicycle클래스에 무슨 특성이 있었죠? hasBasket이라는 저장 프로퍼티가 있었죠?
let tandem = Tandem()
tandem.hasBasket = true
이 hasBasket에는 당연히 접근이 가능하겠죠?
그러면 Bicycle클래스의 슈퍼클래스였던 Vehicle클래스의 특성에는 접근이 가능할까요?
네!!가능합니다. Tandem클래스는 Bicycle클래스의 모든 프로퍼티와 메소드를 상속하며, 이는 다시 Vehicle클래스로 부터 모든 프로퍼티 및 메소드를 상속합니다.
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour
이렇게요.
Overriding
Overriding. 재정의죠?
서브클래스는 슈퍼클래스에서 상속받을 인스턴스 메소드, 타입 메소드, 인스턴스 프로퍼티, 타입 프로퍼티 또는 하위 스크립트에 대한 고유한 사용자 지정 구현을 제공할 수 있습니다. 이것을 overriding(재정의)라고 합니다.
이렇게 하지 않고, 상속 될 특성을 겹쳐 쓰려면 Overriding(재정의) 키워드 앞에 override 접두어를 붙입니다.
이렇게 하면 override(재정의)를 제공하고 , 실수로 일치하는 정의를 제공하지 않는 다는것을 분명히 합니다.
실수로 override하면 예기치 않은 동작이 발생 할 수 있으며, override키워드가 없는 모든 override(재정의)는 컴파일 오류를 일으킵니다.
override키워드는 또한 Swift컴파일러가 override(재정의)를 위해 제공한 것과 일치하는 선언을 슈퍼클래스 또는 슈퍼클래스의 슈퍼클래스 등에 선언했는지 확인하도록 요청합니다. 이 검사는 override(재정의) 정의가 올바른지 확인합니다.
Accessing Superclass Methods, Properties, and Subscripts
Overriding Methods
class Train: Vehicle {
}
자..그럼 위에서 계속~~말했듯이 Train은 이제 Vehicle클래스의 특성에 접근할 수 있게 됩니다.
Vehicle클래스에서 메소드가 하나 있었죠?
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// do nothing
}
}
저 makeNoise()라는 메소드요! 이걸 Train클래스에서 override(재정의)해볼게요.
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
override(재정의)를 하려면 먼저 이름이 무조건 같아야 되겠죠???? 다시 정의하는 거니까요.
그리고 꼭!!!앞에 override키워드를 붙이셔야 합니다.
안붙히면 오류가 나게 돼요! 왜냐면 Swift컴파일러가 Vehicle클래스를 쫙 보기 때문.. Vehicle클래스에 같은 이름이 있다는 걸 발견하면 컴파일 에러를 내게 됩니다.
그리고 알려주죠. 앞에 override키워드를 붙혀!!!!라구요.
let train = Train()
train.makeNoise()
// Prints "Choo Choo"
그럼 이렇게 되겠죠? 이런게 바로 override(재정의)에요. 어렵게 생각하지 마세요 :)
그럼 위에서 말했던
● someMethod()라는 override(재정의) 된 메소드는 override(재정의)된 메소드 구현 내에서 super.someMethod()를 호출하여 someMethod()의 슈퍼클래스 버전을 호출 할 수 있습니다.
super.someMethod()가 무슨소리?
만약 내가 Train클래스에서 Vehicle클래스에 있던 makeNoise()메소드를 override(재정의)하긴 할건데..슈퍼클래스의 구현을 따르고 싶어!!!라고 한다면?
class Train: Vehicle {
override func makeNoise() {
super.makeNoise()
}
}
이렇게 해주면 된다는 거죠. Vehicle클래스의 makeNoise()메소드는 아무것도 정의가 되지 않은 상태였죠?
func makeNoise() {
// do nothing
}
이렇게요!
그럼
let train = Train()
train.makeNoise()
이렇게하면? 아까처럼 "Choo Choo"가 출력될까요?
네!! 아닙니다. 슈퍼클래스. 즉 Vehicle클래스의 정의를 따르겠다고 한거기 때문에 Vehicle클래스의 makeNoise()메소드를 호출한 것과 똑같이 되죠.
Overriding Properties
Overriding Property Getters and Setters
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
자... Vehicle클래스를 상속받았지만, Car에 새로운 gear라는 프로퍼티를 하나 추가해주고,
override키워드가 나왔네요!!!그럼 뭐다? 재정의 ㅇㅇ
Vehicle클래스에 있는 description이라는 이름의 연산프로퍼티를 override(재정의)하고 있습니다.
이름과 타입을 반드시 명시해줘야해요!!! 이름이 같아야하는건 당연하지만 타입도?
라고 생각할 수 있겠지만, 타입을 명시해주지 않으면 컴파일 에러가 난답니다. (슈퍼클래스의 해당 프로퍼티의 타입과 일치해야함)
자..description부분을 조금 더 자세히 볼게요.
override var description: String {
return super.description + " in gear \(gear)"
}
super가 나왔네요!!!그럼 뭐라고 그랬죠? 슈퍼클래스의 구현을 갖다가 쓰겠다ㅏㅏㅏㅏㅏ라는것이죠. 거기에 " in gear \(gear)"String을 추가해서 리턴하는 연산 프로퍼티네요!
Vehicle클래스의 description의 원래 구현이 어떻게 되어있었죠?
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
이렇게 되어 있었죠?
그럼..Car클래스의 인스턴스를 만들어서 description에 접근하면?
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3
이렇게 되겠네요! 만약
let car = Car()
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3
이렇게만 되어있으면 뭐가 출력이 될까요~~
Car: traveling at 0.0 miles per hour in gear 1
class Car: Vehicle {
var gear = 1
override var currentSpeed: Double = 2.0
}
이렇게 하면 될까요? 하지만 위 코드는 컴파일 에러를 일으킵니다.
왜냐? "프로퍼티"는 저장프로퍼티로 override(재정의) 할 수 없거든요 :)
그 프로퍼티가 저장이었든, 연산프로퍼티였든 override(재정의)를 저장프로퍼티로 하려고 하면 오류입니다!
?? (저장/연산)프로퍼티를 연산프로퍼티로 override(재정의)하는 건 가능한가요?
class Car: Vehicle {
var gear = 1
override var currentSpeed: Double {
get{
return 1.0
}
set{
gear = Int(super.currentSpeed)
}
}
}
class Vehicle {
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
}
class Car: Vehicle {
override var description: String {
return super.description + " in gear \(gear)"
}
}
읽기전용 -> 읽기전용으로 override(재정의)했지만, 이걸 읽기/쓰기 프로퍼티로 만들 수 있다는 거죠.
class Car: Vehicle {
var gear = 1
var returnStr = ""
override var description: String {
get{
return super.description + " in gear \(gear)"
}
set{
returnStr = newValue
}
}
}
이렇게요. newValue가 뭐여;;하시는 분들은 <프로퍼티 옵저버>글을 참고해주세요 :)
아무튼 지금 중요한건!!! 읽기전용 프로퍼티는 상속하는 서브클래스 내에서 읽기/쓰기 프로퍼티로 override(재정의)할 수 있다!!!라는 것이죠.
Overriding Property Observers
이게 뭔지 알아봅시다.
프로퍼티 override(재정의)를 사용하여 상속된 프로퍼티에 프로퍼티 옵저버를 추가할 수 있습니다.
이렇게 하면 상속 된 프로퍼티의 값이 변경 될 때, 해당 프로퍼티가 처음 구현된 것과 상관없이 알림을 받을 수 있습니다.
상속된 상수 저장 프로퍼티 또는 읽기전용 연산프로퍼티에는 프로퍼티 옵저버를 추가할 수 없습니다.
그렇기 때문에, override(재정의)의 일부로서, willSet또는, didSet의 구현을 제공하는 것을 적절하지 않습니다.
또한 동일한 프로퍼티에 대해 override setter와 override 프로퍼티 옵저버를 둘다 제공할 수는 없습니다.
프로퍼티 값의 변경사항을 확인하려는 경우, 해당 프로퍼티에 대한 사용자 지정 setter를 이미 제공하고 있는 경우, 사용자 지정 setter내에서 값 변경 내용을 관찰하기만 하면 됩니다.
자..위에서 언급했죠?
"상수 저장 프로퍼티 또는 읽기전용 연산프로퍼티에는 프로퍼티 옵저버를 추가할 수 없습니다."
상수 저장프로퍼티와 읽기전용 연산프로퍼티에는 프로퍼티 옵저버를 추가할 수 없다!!!!!!!!!!!!!!
1) 상수저장프로퍼티에 프로퍼티 옵저버를 추가 할 수 없는 이유 : 프로퍼티 옵저버엔 willSet, DidSet이 있는데, 애초에 Set이 안됨. 왜냐? 상수니까. 변경이 안된다.
2 ) 읽기전용 연산프로퍼티에 프로퍼티 옵저버를 추가 할 수 없는 이유 : 말그대로 "읽기전용". 프로퍼티 옵저버에는 willSet, DidSet이 있는데, 애초에 Setter가 없으므로. (읽기전용이므로) Setter가 없음 -> willSet, didSet을 추가할 이유가 없음;; Set이 안되니까!!!!!!
그 외에는 다 추가할 수 있나보네요.
예제를 통해 봅시다.
자..Car라는 클래스를 정의했었죠?
그럼 AutomaticCar라는 클래스를 하나 정의해봅시다. 물론 Car라는 클래스를 상속받아서요.
이 AutomaticCar는 자동 기어박스가 있어서, 현재 속도를 기반으로 사용할 기어를 자동으로 선택하는 자동차에요.
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
}
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
자..Vehicle클래스에서 currentSpeed는 저장프로퍼티였죠? 근데 상수? 아니죠. 변수입니다!!!!
그러므로? ==> 프로퍼티 옵저버를 추가 할 수 있음 ㅇㅇ
근데 description이라는 연산프로퍼티는?? 읽기전용이죠??????
그럼 프로퍼티옵저버 추가 가능? ==> ㄴㄴ
그래서 currentSpeed만 프로퍼티 옵저버를 추가한 것을 볼 수 있죠.
willSet은 안부르고, didSet만 불렀네요!
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
자..gear는 어디클래스의 gear를 나타낼까요!?!?!?!?1 네!! Car클래스의 gear라는 저장프로퍼티를 나타내죠.
그 gear에!! 뭐 어떻게 연산해서 값을 넣어주네요. didSet에 oldValue가 안쓰이고, currentSpeed가 쓰였네요.
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
automatic자동차의 현재속도를 지정해주면, 알아서 기어를 맞춰주네요.
Preventing Overrides
class Vehicle {
final var currentSpeed = 0.0
}
만약 currentSpeed가 final이라면!!!
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
print(oldValue)
gear = Int(currentSpeed / 10.0) + 1
}
}
}
아까의 우리의 시도는 컴파일에러를 일으키게됩니다. 왜냐하면 간단합니다.
currentSpeed를 override(재정의)하려고 하니까 ㅇㅇ
final class Vehicle { }
class Bicycle: Vehicle { } //Error!
이렇게 class앞에 final이 붙으면 애초에 서브클래싱이 불가능해지게 됩니다!! 조금 감이 오시나요?
그리고 왜 프로토콜에서 init을 요구했을 때, 그 프로토콜을 채택한 클래스가 final이라면 required가 왜 필요없는지!!!도 아시겠죠?
자세한 사항은 < Protocol (2) >를 참고해주세요 :)
역시 상속을 쓴다쓴다 했지만..새롭게 알아가는 사실들이 몇개 있네요 :)
그런데 궁금한점이 하나 있는데...
분명 "Property observers can be added to any property, regardless of whether it was originally defined as a stored or computed property."라고..(프로퍼티 옵저버는 원래 저장 또는 연산 프로퍼티로 정의되었는지 여부에 관계없이 모든 프로퍼티에 추가할 수 있습니다.) 라고 했는데. 위에서 여기서는 상수저장프로퍼티와 읽기전용 연산프로퍼티에는 프로퍼티 옵저버를 추가할 수 없다고 그러네요. 물론 상수저장프로퍼티와 읽기전용 연산프로퍼티에는 프로퍼티 옵저버를 추가할 수 없다가 맞지만..왜 처음에 저렇게 나오는지 잘 모르겠네요 :)...
----->아 알았어요.
일단 프로퍼티 옵저버는 lazy저장프로퍼티를 제외하고 저장프로퍼티에만 추가가 가능한데, 서브클래스는 해당 프로퍼티가 저장프로퍼티인지, 연산프로퍼티인지 모르고 그냥 특정 이름과 타입만 알 수 있다고 그랬죠? 그래서 원래 프로퍼티가 저장프로퍼티가 아니라 연산프로퍼티로 정의되었다고 해도!!!!!! 서브클래스에서는 해당 연산프로퍼티를 재정의해서 프로퍼티 옵저버를 추가 할 수 있는 것이죠. 하지만 해당 연산프로퍼티가 반드시 읽기/쓰기 프로퍼티여야한다는 것!!!
읽기전용이면 프로퍼티 옵저버가 소용이 없겠죠? (애초에 에러남)
class Vehicle {
var currentSpeed = 0.0
var str = ""
var description: String {
get{
return "traveling at \(currentSpeed) miles per hour"
}
set{
str = "zedd"
}
}
}
뭐 이렇게 description이 이렇게 읽기/쓰기 연산프로퍼티였어요. 원래 연산프로퍼티에는 프로퍼티 옵저버를 추가 할 수 없지만!!!
class Bicycle: Vehicle {
override var description: String {
didSet{
print(oldValue)
}
}
}
이렇게 override(재정의)하면, description이 연산프로퍼티였다하더라도!!!!!프로퍼티 옵저버를 추가할 수 있게 되는것이죠.
아하아하
아무튼 오늘도 도움이 되었으면 좋겠습니다 :)
'Swift' 카테고리의 다른 글
Swift ) Access Control(접근제어) - (2) (1) | 2018.01.17 |
---|---|
Swift ) Sequences와 Lazy (2) | 2018.01.16 |
Swift ) Access Control(접근 제어) - (1) (4) | 2018.01.13 |
Swift ) 왕초보를 위한 Codable / JSON Encoding and Decoding (9) | 2018.01.06 |
Swift4 ) Swap / Law of Exclusivity (0) | 2017.12.21 |
- FLUTTER
- swift 공부
- WWDC
- np-complete
- 스위프트 문법
- IOS
- WidgetKit
- SwiftUI
- 제이슨 파싱
- actor
- Combine
- 회고
- swift array
- 피아노
- ios 13
- UIBezierPath
- swift sort
- np-hard
- WKWebView
- github
- swift delegate
- 스위프트
- Git
- Accessibility
- fastlane
- swift tutorial
- iOS delegate
- Xcode
- Swift
- swift3
- Total
- Today
- Yesterday