[Swift] Day10 - Classes

한철희·2023년 3월 1일
0

100 DaysOfSwift

목록 보기
6/11

벌써 10일차 까지왔네요
클로저, 구조체에 이어서 클래스 차례입니다
꾸준히 열심히 가보겠습니다


클래스는 구조체와 상당히 비슷하게 생겼습니다
데이터타입을 만들고 프로퍼티와 메소드를 만드는것까지 닮아있죠
그러나 클래스는 '상속'이라는 특징을 가집니다.
이게 클래스의 강력한 기능이고, 실제 프로젝트에서도 많이 사용하게 될겁니다


Creating your own classes

위에서 한번 언급했지만 클래스는 구조체와 비슷합니다.
그러나 큰 5가지 차이점이 있는데요 이번 강의에서 하나씩 짚고 넘어갈게요

첫 번째 차이점은 클래스는 memberwise initializer가 없습니다
즉, 클래스에서는 프로퍼티를 만들때마다 초기화를 해줘야한다는 거죠

class Dog {
    var name: String
    var breed: String

    init(name: String, breed: String) {
        self.name = name
        self.breed = breed
    }
}

Dog 클래스에 namebreed프로퍼티가 있네요.
둘 다 초기화를 해줍니다.

인스턴스를 생성할 때는 구조체와 똑같이 해주면 됩니다.

let poppy = Dog(name: "Poppy", breed: "Poodle")


Class inheritance

두 번째 차이점은 클래스의 꽃이라고도 할 수 있는 '상속'입니다.
'상속'은 이미 존재하는 클래스에 기초해서 새로운 클래스를 만들 수 있는 방법입니다.
이걸 '상속받는다'라고 보통 얘기하는데요 상속받은 클래스는 원래 클래스의 프로퍼티와 메소드를 기본적으로 가지게 됩니다.
이때 상속받는 클래스를 자식 클래스 원래의 클래스는 부모클래스라고 합니다

앞에서 Dog클래스를 만들었죠?

class Dog {
    var name: String
    var breed: String

    init(name: String, breed: String) {
        self.name = name
        self.breed = breed
    }
}

그리고 상속을 활용해서 Poodle이라는 클래스를 만들 수 있죠

class Poodle: Dog {

}

자식클래스: 부모클래스로 작성해주면 됩니다.

PoodleDog클래스의 프로퍼티를 상속받는데요
따로 초기화를 해줄수도 있답니다.
클래스 이름부터 Poodle이니까 강아지 종이 푸들인건 정해져있겠죠?
그럼 name프로퍼티만 필요합니다.
Dog의 생성자를 사용해서 Poodle의 생성자를 만들 수 있으면 더 좋겠죠?

class Poodle: Dog {
    init(name: String) {
        super.init(name: name, breed: "Poodle")
    }
}

스위프트는 안전을 이유로 자식 클래스에서 항상 super.init()을 사용하도록 합니다

아 참고로 스위프트에서는 한글사용이 비교적 자유롭습니다


Overriding methods

자식 클래스는 부모의 메소드를 변경할 수 있는데요 이를 '오버라이딩' 이라고 합니다.
예시로 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 classes

클래스 상속은 굉장히 유용하지만 다른 사람들이 내가 만든 클래스에 기초해서 클래스 만드는 것을 불허하고 싶을 때가 있지요

그래서 스위프트는 final키워드를 제공합니다.
final로 클래스를 작성하면 다른 클래스들이 상속을 할 수가 없습니다
이 말은 다른 사람들이 내 코드를 수정(오버라이딩)해서 사용하지 못하고
있는 그대로 사용해야한다는 것이죠

final class Dog {
    var name: String
    var breed: String

    init(name: String, breed: String) {
        self.name = name
        self.breed = breed
    }
}

확인차 Dog을 상속받는 Poodle을 생성해보려 하지만 에러가 뜨는 걸 확인할 수 있습니다.


Copying objects

세 번째 차이점은 복사되는 방식의 차이입니다.
구조체를 복사하면 원본과 복사본은 다른 개체로 봅니다
하지만 클래스는 원본과 복사본이 같은 지점을 가리키기 때문에
한 쪽을 변경하면 다른 쪽도 변경됩니다.

간단한 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

네 번째 차이점은 클래스에 초기화해지(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!")
}


생성될 때 출력을 하고 메서드를 작동한 다음에 해지될 때 초기화해지 부분을 작동합니다


Mutability

마지막 차이점은 상수를 다루는 방법입니다.
구조체에서는 상수 구조체에 변수 프로퍼티가 있으면 구조체 자체가 상수이기 때문에 프로퍼티의 변경이 불가합니다

반면에 상수 클래스에 변수 프로퍼티라면 프로퍼티의 변경이 가능합니다
이런 이유 때문에 클래스는 프로퍼티를 변경하기 위한 mutating키워드가 불필요합니다. 구조체에서만 쓰이게 되죠

이 말은 클래스 내부의 변수 프로퍼티는 모두 변경이 가능하다는 뜻입니다.

class Singer {
    var name = "Taylor Swift"
}

let taylor = Singer()
taylor.name = "Ed Sheeran"
print(taylor.name)

위의 예시에서도 상수 인스턴스를 선언했지만 name프로퍼티는 변수이므로 값의 변경이 가능합니다

만약 이렇게 작동하지 않도록 하고싶다면 프로퍼티를 상수로 만들면 됩니다

class Singer {
    let name = "Taylor Swift"
}


변경이 안 되는 부분까지 확인 해 봤습니다.


Summary

  1. 클래스와 구조체는 자신만의 데이터 타입을 만들 수 있고, 프로퍼티와 메서드를 가진다는 점에서 비슷합니다
  2. 하나의 클래스는 다른 클래스부터 상속받을 수 있습니다. 그리고 부모 클래스의 프로퍼티와 메서드 전부를 상속받습니다.
  3. 클래스를 final키워드로 마킹할 수도 있는데요 다른 클래스가 상속받지 못하도록 막아줍니다
  4. 메서드 오버라이딩은 자식 클래스가 부모클래스의 메소드를 새로운 추가점과 함께 사용할 수 있도록 해줍니다
  5. 두 변수가 같은 클래스 인스턴스를 가리키면 같은 메모리를 사용하기 떄문에 하나의 값을 변경하면 다른 값도 변경됩니다
  6. 클래스는 deinitializer(초기화해지) 가 가능합니다. 클래스의 인스턴스가 해지될 때 작동하는 코드지요
  7. 클래스는 구조체만큼 상수를 강요하지 않습니다. 프로퍼티가 변수일 경우
    인스턴스가 어떻게 생성되든 프로퍼티를 변경할 수 있습니다.
profile
초보 개발자 살아남기

0개의 댓글