10일차 - 21.06.17

수킴·2021년 6월 17일
0

100DaysOfSwift

목록 보기
11/37
post-thumbnail

학습키워드

  • classes
  • inheritance

1. Creating your own classes

클래스는 구조체와 유사해보이지만 여러 차이점이 있습니다.

  1. 가장 큰 차이점은 구조체는 값 타입이며 클래스는 참조타입이라는 것입니다. (구조체 복사본은 항상 고유하지만 클래스 복사본은 동일한 데이터를 가리킵니다.)

  2. 클래스는 또한 상속이 가능합니다.

  3. 클래스는 속성을 가지고 있는경우 멤버와이즈 이니셜라이저를 제공하지 않습니다. 따라서 직접 만들어야 합니다.

    class Dog {
        var name: String
        var breed: String
    
        init(name: String, breed: String) {
            self.name = name
            self.breed = breed
        }
    }
    
    let poppy = Dog(name: "Poppy", breed: "Poodle")
  4. 클래스에는 인스턴스가 소멸될때 호출되는 메서드인 deinitializer가 있습니다. (구조체 ❌ )

  5. 상수 클래스의 변수 속성은 자유롭게 수정할 수 있습니다. 상수 구조체의 변수 속성은 수정할 수 없습니다.

2. Class inheritance

클래스 상속,(Subclassing)이란 한 클래스를 다른 클래스의 기반 위에 구축하는 기능입니다. (클래스의 모든 속성과 메서드를 상속합니다.)

  • (상속을 하는, 상위에 있는 , 기능을 물려주는 )클래스를 (부모, 슈퍼, 상위) 클래스라고합니다.
  • (상속을 받은, 하위에 있는, 기능을 물려받은 ) 클래스를 (자식, 서브, 하위) 클래스라고합니다.
  • 스위프트는 하나의 클래스에서만 상속을 지원합니다.
class Dog {
    var name: String
    var breed: String

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

class Poodle: Dog {
    init(name: String) {
        super.init(name: name, breed: "Poodle")
    }
}
  • Dog클래스에서 상속받은 Poodle클래스는 자체 이니셜라이저를 작성할 수 있습니다. 항상 품종이 Poodle 이기 때문에 좀 더 간단하게 작성할 수 있습니다.
  • super는 부모 클래스의 속성 및 메서드에 접근할 때 사용할 수 있는 키워드
  • super는 상속받은 자식 클래스에서 부모 클래스에 접근하기 위해서 사용합니다
  • 스위프트는 안전상의 이유로 super.init() 을 자식클래스에서 호출하도록 합니다. 부모클래스가 생성될 때 중요한 작업을 수행할 경우에 대비해야 하기 때문입니다.

클래스에서는 멤버와이즈이니셜라이저를 제공하지 않는 이유가 무엇일 까?

가장 큰 이유는 클래스에 상속이 있기 때문에 멤버 별 이니셜라이저를 사용하기 어렵기 때문입니다.

만약에 사용한다면 상속받은 클래스를 만든 다음 나중에 클래스에 속성을 추가하면 에러가 발생할 것이기 때문입니다.

3. Overriding methods

자식클래스는 부모클래스의 메서드를 자신이 원하는 기능으로 구현할 수 있습니다.

재정의한다는 것은 부모클래스의 메서드 구현을 자신의 것으로 대체한다는 것입니다.

사용방법은 overriding 키워드를 func 앞에 붙여줍니다.

class Dog {
    func makeNoise() {
        print("Woof!")
    }
}

class Poodle: Dog {
    override func makeNoise() {
        print("Yip!")
    }
}

let poppy = Poodle()
poppy.makeNoise()

// 실행결과
Yip!
  • 부모클래스에서 없는 메서드를 오버라이드메서드로 재정의 하려고 하면 오류가 발생합니다.

언제 오버라이드를 사용하는 것일까?

모든 동작을 처음부터 다시 작성하는 것보다 만들어놓은 것보다 일부분만 변경하는 경우에 좀 더 편리할 것입니다. 따라서, 모든 동작을 유지하고 사용자가 자식클래스에서 작은 부분을 변경하는 경우에 사용합니다.

4. Final classes

클래스가 상속되는 것을 제한하고 싶은 경우 final 키워드를 사용하여 최종클래스라고 선언하면 다른 클래스에서 상속할 수 없습니다. → 당연히, 메서드 재정의도 할 수 없습니다.

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

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

어떤 경우 final로 선언해야 할까?

만약 상속을 제한하지 않는다면 클래스를 상속하면 속성과 메서드를 사용할 수 도있지만 수정할 수 있는 가능성이 있기 때문에 변경되어서는 안되는 작업등을 막기위한 예방책이 될 수 있습니다.

또한 최종클래스가 최종클래스가 아닌 클래스보다 성능이 최적화 되어있습니다. (static dispatch)

5. Copying objects

클래스와 구조체의 차이점 중 복사방법에 대한 차이점이 있었습니다.

구조체의 원본과 복사본이 있는 경우 복사본의 값을 변경하면 원본은 변경되지 않습니다.

반면에, 클래스의 원본과 복사본이 있는경우 복사본의 값을 변경하면 원본도 같이 변경됩니다.

// MARK: - class
class Singer {
    var name = "Taylor Swift"
}

var singer = Singer()

var singerCopy = singer
singerCopy.name = "Justin Bieber"

print(singer.name)
print(singerCopy.name)

// 실행결과
Justin Bieber
Justin Bieber

// MARK: - struct
struct Singer {
    var name = "Taylor Swift"
}

var singer = Singer()

var singerCopy = singer
singerCopy.name = "Justin Bieber"

print(singer.name)
print(singerCopy.name)

// 실행결과
Taylor Swift
Justin Bieber

❗️ 클래스 인스턴스를 만드는 시점이 중요

class Statue {
    var sculptor = "Unknown"
}
var venusDeMilo = Statue()
venusDeMilo.sculptor = "Alexandros of Antioch"
var david = Statue()
david.sculptor = "Michaelangelo"
print(venusDeMilo.sculptor)
print(david.sculptor)

// 실행결과
Alexandros of Antioch
Michaelangelo
  • 각각 클래스 인스턴스를 만들었기 때문에 sculptor 속성의 값은 다르게 출력됩니다.

class Statue {
    var sculptor = "Unknown"
}
var example = Statue()
var venusDeMilo = example
venusDeMilo.sculptor = "Alexandros of Antioch"
var david = example
david.sculptor = "Michaelangelo"
print(venusDeMilo.sculptor)
print(david.sculptor)

// 실행결과 
Michaelangelo
Michaelangelo
  • 인스턴스를 저장한 후 같은 인스턴스를 가리키고 있기 때문에 sculptor 속성의 값은 같게 출력됩니다.

구조체 (값 타입) vs 클래스 (참조 타입)

  • 구조체는 값이 저장되면 변수안에 완전히 포함되고 다른 값과 공유하지 않습니다.
    모든 데이터가 각 변수에 직접 저장되므로 복사할 때 모든 데이터의 전체 복사본을 저장합니다.
    - wwdc 구조체 저장 방식 예제
  • 클래스는 값이 저장되는 것이 아니라 실제 메모리가 있는 주소를 저장합니다.
    - wwdc 클래스 저장 방식 예제

→ 이런 특성을 사용해서 여러 장소에서 값이 변경되는 경우를 원하지 않는다면 구조체를 사용하는 것을 선호하고 하나의 값을 여러곳에서 공유하기를 원한다면 클래스를 사용하는 것이 좋습니다.

6. Deinitializers

클래스는 인스턴스가 사라질 때 실행되는 코드 Deinitializer 를 제공합니다. ( 구조체는 ❌ )

사용 방법은 deinit 키워들를 사용하여 코드블럭 안에 원하는 코드를 작성합니다.

class Person {
    var name = "John Doe"

    init() {
        print("\(name) is alive!")
    }

    func printGreeting() {
        print("Hello, I'm \(name)")
    }
    
    deinit {
        print("\(name) is no more!")
    }
}

for _ in 1...2 {
    let person = Person()
    person.printGreeting()
}

// 실행결과
John Doe is alive!
Hello, I'm John Doe
John Doe is no more!
John Doe is alive!
Hello, I'm John Doe
John Doe is no more!
  • 반복문에서 Person 클래스의 인스턴스가 사라질 때 마다 deinit 코드를 실행합니다

왜 클래스에만 Deinitializers를 제공하는 이유가 무엇일까?

→ 인스턴스의 복사방식의 차이

  • 구조체는 소유하고 있는 것이 더 이상 존재하지 않을 때 파괴됩니다. (즉, 구조체인스턴스를 사용하고 있는 것이 사라질 때 함께 사라집니다. 구조체는 각각의 인스턴스가 자체 데이터가 있으므로 사라져도 문제가 없습니다.

  • 반면에, 클래스는 복사방식이 복잡하기 때문에 여러 복사본이 다양한 장소에 존재할 수 있습니다. 모든 복사본이 동일한 장소를 가리키므로 실제 클래스인스턴스가 사라지는지 알기가 어렵습니다.

    → Swift ARC 기능

    ARC가 클래스 인스턴스의 복사본의 개수를 추적한 후 복사본을 가져올 떄마다 참조횟수 1을 더하고 복사본이 사라질 때마다 1을 뺍니다. 참조횟수가 0에 도달하면 아무도 참조하지 않는다는 의미이므로 deinitializer를 호출하고 인스턴스가 사라집니다.

7. Mutability

상수로 클래스를 선언하여도 클래스내부 속성이 변수라면 값을 변경할 수 있습니다. (구조체에서 mutating 을 사용하여 값을 변경하는 방식과 비교됩니다.)

class Singer {
    var name = "Taylor Swift"
}

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

상수클래스에서 변수 속성을 변경할 수 있는 이유가 무엇일까?

var number = 5
number = 6

예를 들어 구조체는 number에 5를 저장한 후 기존값을 제거한 후 6을 저장합니다. 구조체는 속성 중 하나를 변경하면 전체 구조체가 변경됩니다.

링크

100 Days of Swift - Day 10 - Hacking with Swift

profile
iOS 공부 중 🧑🏻‍💻

0개의 댓글