벌써 10일차 까지왔네요
클로저, 구조체에 이어서 클래스 차례입니다
꾸준히 열심히 가보겠습니다
클래스는 구조체와 상당히 비슷하게 생겼습니다
데이터타입을 만들고 프로퍼티와 메소드를 만드는것까지 닮아있죠
그러나 클래스는 '상속'이라는 특징을 가집니다.
이게 클래스의 강력한 기능이고, 실제 프로젝트에서도 많이 사용하게 될겁니다
위에서 한번 언급했지만 클래스는 구조체와 비슷합니다.
그러나 큰 5가지 차이점이 있는데요 이번 강의에서 하나씩 짚고 넘어갈게요
첫 번째 차이점은 클래스는 memberwise initializer
가 없습니다
즉, 클래스에서는 프로퍼티를 만들때마다 초기화를 해줘야한다는 거죠
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
Dog 클래스에 name
와breed
프로퍼티가 있네요.
둘 다 초기화를 해줍니다.
인스턴스를 생성할 때는 구조체와 똑같이 해주면 됩니다.
let poppy = Dog(name: "Poppy", breed: "Poodle")
두 번째 차이점은 클래스의 꽃이라고도 할 수 있는 '상속'입니다.
'상속'은 이미 존재하는 클래스에 기초해서 새로운 클래스를 만들 수 있는 방법입니다.
이걸 '상속받는다'라고 보통 얘기하는데요 상속받은 클래스는 원래 클래스의 프로퍼티와 메소드를 기본적으로 가지게 됩니다.
이때 상속받는 클래스를 자식 클래스 원래의 클래스는 부모클래스라고 합니다
앞에서 Dog클래스를 만들었죠?
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
그리고 상속을 활용해서 Poodle이라는 클래스를 만들 수 있죠
class Poodle: Dog {
}
자식클래스: 부모클래스
로 작성해주면 됩니다.
Poodle은 Dog클래스의 프로퍼티를 상속받는데요
따로 초기화를 해줄수도 있답니다.
클래스 이름부터 Poodle이니까 강아지 종이 푸들인건 정해져있겠죠?
그럼 name
프로퍼티만 필요합니다.
Dog
의 생성자를 사용해서 Poodle
의 생성자를 만들 수 있으면 더 좋겠죠?
class Poodle: Dog {
init(name: String) {
super.init(name: name, breed: "Poodle")
}
}
스위프트는 안전을 이유로 자식 클래스에서 항상 super.init()
을 사용하도록 합니다
아 참고로 스위프트에서는 한글사용이 비교적 자유롭습니다
자식 클래스는 부모의 메소드를 변경할 수 있는데요 이를 '오버라이딩' 이라고 합니다.
예시로 Dog클래스에 makeNoise() 메서드를 만들어 보죠
class Dog {
func makeNoise() {
print("Woof!")
}
}
Dog
을 상속받는 Poodle
클래스를 만들면 makeNoise()
메서드 또한 상속되겠죠?
class Poodle: Dog {
}
let poppy = Poodle()
poppy.makeNoise()
그럼 이 코드도 "Woof!" 출력할겁니다.
여기서 오버라이딩을 하면 내용을 변경할 수 있어요
메소드 오버라이딩을 할 때 스위프트는 func
보단 override func
를 사용하는걸 권장합니다
class Poodle: Dog {
override func makeNoise() {
print("Yip!")
}
}
오버라이딩을 한 후 poppy.makeNoise()
을 하면
이제 "Yip!"을 출력할 겁니다
클래스 상속은 굉장히 유용하지만 다른 사람들이 내가 만든 클래스에 기초해서 클래스 만드는 것을 불허하고 싶을 때가 있지요
그래서 스위프트는 final
키워드를 제공합니다.
final
로 클래스를 작성하면 다른 클래스들이 상속을 할 수가 없습니다
이 말은 다른 사람들이 내 코드를 수정(오버라이딩)해서 사용하지 못하고
있는 그대로 사용해야한다는 것이죠
final class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
확인차 Dog
을 상속받는 Poodle
을 생성해보려 하지만 에러가 뜨는 걸 확인할 수 있습니다.
세 번째 차이점은 복사되는 방식의 차이입니다.
구조체를 복사하면 원본과 복사본은 다른 개체로 봅니다
하지만 클래스는 원본과 복사본이 같은 지점을 가리키기 때문에
한 쪽을 변경하면 다른 쪽도 변경됩니다.
간단한 Singer 클래스로 확인해보죠
class Singer {
var name = "Taylor Swift"
}
기본 값을 가지는 name
프로퍼티가 있죠
var singer = Singer()
print(singer.name)
인스턴스를 생성해서 출력해보면 "Taylor Swift"를 출력할 겁니다.
이제 첫번째(singer 변수) 를 두번째 변수(singerCopy)에 할당해서
이름을 바꿔볼게요
var singerCopy = singer
singerCopy.name = "Justin Bieber"
그리고 클래스에서 원본과 복사본은 같은 메모리를 가르키기 때문에
singer
변수를 다시 호출하면 singerCopy
와 마찬가지로 "Justin Bieber"를 출력합니다.
print(singer.name)
만약에 구조체에서 해보면 원본과 복사본이 다른 값을 출력할겁니다
네 번째 차이점은 클래스에 초기화해지(Deinitializers)가 존재합니다
클래스의 인스턴스가 해지될 때 작동하는 코드지요
Person 클래스를 예시로 들어볼게요
class Person {
var name = "John Doe"
init() {
print("\(name) is alive!")
}
func printGreeting() {
print("Hello, I'm \(name)")
}
}
반복문을 이용해서 인스턴스를 몇 개 생성해보죠
반복문이 진행할 때마다 생성되고 해지될거에요
for _ in 1...3 {
let person = Person()
person.printGreeting()
}
그리고 이제 초기화해지 부분입니다
Person인스턴스가 해지될 때마다 작동할겁니다
deinit {
print("\(name) is no more!")
}
생성될 때 출력을 하고 메서드를 작동한 다음에 해지될 때 초기화해지 부분을 작동합니다
마지막 차이점은 상수를 다루는 방법입니다.
구조체에서는 상수 구조체에 변수 프로퍼티가 있으면 구조체 자체가 상수이기 때문에 프로퍼티의 변경이 불가합니다
반면에 상수 클래스에 변수 프로퍼티라면 프로퍼티의 변경이 가능합니다
이런 이유 때문에 클래스는 프로퍼티를 변경하기 위한 mutating
키워드가 불필요합니다. 구조체에서만 쓰이게 되죠
이 말은 클래스 내부의 변수 프로퍼티는 모두 변경이 가능하다는 뜻입니다.
class Singer {
var name = "Taylor Swift"
}
let taylor = Singer()
taylor.name = "Ed Sheeran"
print(taylor.name)
위의 예시에서도 상수 인스턴스를 선언했지만 name
프로퍼티는 변수이므로 값의 변경이 가능합니다
만약 이렇게 작동하지 않도록 하고싶다면 프로퍼티를 상수로 만들면 됩니다
class Singer {
let name = "Taylor Swift"
}
변경이 안 되는 부분까지 확인 해 봤습니다.
final
키워드로 마킹할 수도 있는데요 다른 클래스가 상속받지 못하도록 막아줍니다