swift study 13. 상속(Inheritance)과 초기화(Initialization)

jess·2022년 7월 18일
0

Swift

목록 보기
13/19
post-thumbnail

🍃 출처 : 앨런 swift 문법 마스터 스쿨 수업을 듣고 제가 이해한대로 정리해서 올리는 포스팅입니다.

1️⃣ 클래스의 상속과 재정의

📓 상속 (Inheritance)

  • 오로지 클래스만 상속이 가능하며, 상속은 단일 상속만 허용한다. (다중상속X)
  • 클래스는 메서드, 속성 및 기타 특징들을 상속할 수 있다
✨ 수직확장
   : 본질적으로 성격이 비슷한 타입을 새로 만들어
      1) 데이터(저장속성)를 추가하거나
      2) 기능(메서드)를 변형시켜서 사용하려는 것 

예시

// 슈퍼클래스
class Person() {
    var id = 0
    var name = "이름"
    var email = "abc@email.com"
}

⬇️ 상속

// 서브클래스
class Student: Person {
    // id 변수존재
    // name 변수존재
    // email 변수존재
    var studentId = 0
}

✏️ 상속의 기본 문법

class AClass {
    var name = "이름"
}
class BClass: AClass {
    var id = 0
}
let b = BClass()
b.id
b.name
  • 상위 클래스에서 존재하는 멤버를 변형하는것. 즉, 재정의
  • BClass는 AClass를 상속해서 구현
  • 서브 클래스는 슈퍼클래스로부터 멤버를 상속
  • 클래스 상속금지 키워드 : Final
    ex) final class AClass {
    final var name = "이름"
    ...

2) 재정의 (overriding)

  : 서브클래스에서 슈퍼클래스의 동일한 멤버를 변형하여 구현
- 오버로딩 (overloading) : 과적. 함수 하나에 여러가지 함수 대응
- 오버라이딩 (overriding) : 재정의

1) 속성 : 저장속성 재정의 X
2) 메서드 : 메서드, 서브 스크립트, 생성자 ➡️ 재정의 방식이 다름

예시

class Human {
    func description() {
        print("나는 사람입니다.")
    }
}
class Teacher: Human {
    // 한 클래스 내에서 동일 메서드를 두 개 선언할 수 없으니 재정의 해줌
    override func description() {
        print("나는 선생님입니다.")
    }
}
let jess = Teacher()
jess.description()

✏️ 재정의 규칙

대원칙
1) 저장 속성의 재정의 불가 (상속가능) / 즉, 변형시켜서 사용할 수 없다
2) 메서드는 제정의 가능 (다만, 기능 확장만 가능)

✏️재정의의 기본 문법

class SomeSuperClass {
    // 저장 속성
    var aValue = 0
    // 메서드
    func doSomething() {
        print("Do Something")
    }
}
class SomeSubClass: SomeSuperClass {
    // 저장속성 ➡️ (재정의) 계산속성
    override var aValue: Int {
        get {
            return 1
        }
        set {
            super.aValue = newValue
        }
    }
}
override func doSomething() {
    // 상위 구현 호출 
    super.doSomething()
    print("Do Something2")
}

2-1) 저장 속성

    var id = 0
    var name = "이름"
    var email = "abc@email.com"

<대원칙>
- ⭐️ 재정의 불가

  • 메모리 구조에서 상위 구현을 창조하기 때문에 재정의(변형) 불가
    class Human {
      var name = "Jess"
    }
    class Teacher: Human {
      override var name: String = "Felix"    // 🚨컴파일에러
    }

<예외>

  • 메서드 형태로 부수적 추가는 가능 (메모리 구조를 건드리지 않는 형태로는 가능)
    (1) 읽기/쓰기 계산속성 형태로 재정의 가능
    (메서드 추가 형태로 구현)
class Human {
    var name = "Jess"
}
class Teacher: Human {
    override var name: String {
        get {
            return self.name
        }
        set {
            self.name = newValue
        }
    }
}
  • 이렇게 getter / setter를 모두 구현해주면 오버라이딩이 됨

    (2) 속성 감시자 형태로 재정의 가능
    (실질적 단순 메서드 추가)

     

2-2) 계산 속성

: 실질적 메서드

  • 확장 방식의 재정의 가능
    (1) 읽기 ➡️ 읽기/쓰기 가능 (기능 확장 가능)
    (2) 읽기/쓰기 ➡️ 읽기는 불가능 (기는 축소 불가능)
  • 속성 감시자를 추가하는 재정의 기능 (실질적 단순 메서드 추가)

2️⃣ 초기화의 과정과 생성자

📓 초기화 (Initialization) 12-3

  • 구조체 / 클래스 / (열거형)
  • 인스턴스를 생성하는 과정
  • 각 "저장속성"에 대한 초기값을 설정하여 인스턴스를 사용가능한 상태로 만드는 것
    (열거형은 저장속성이 존재하지 않으므로, case중에 한가지를 선택 및 생성)
  • 결국, 이니셜라이저의 실행이 완료되었을 때,
    인스턴스의 모든 저장속성이 초기값을 가지는 것이 생성자의 역할.
class Dog {
    var name: String
    var weight: Double
}  
...
// 초기화 메서드 (생성자)
init(name: String, weight: Double) {
    self.name = name
    self.weight = weight
}
...
}

✏️ 초기화의 방법(저장속성이 초기값 가져야 함)
1) 저장 속성의 선언과 동시에 값을 저장
2) 저장 속성을 옵셔널로 선언 (초기값이 없어도 nil로 초기화)
3) 생성자에서 값을 초기화

  • 반드시 생성자를 정의해야 하는 것은 아님.
    이니셜라이저를 구현하지 않아도, 컴파일러는 기본생성자를 자동으로 생성 ➡️ init()
    ✨ 이니셜라이저 구현하면 기본 생성자를 자동으로 생성하지 않음

✏️ 멤버와이즈 이니셜라이저
-구조체

struct Color1 {
    var red: Double = 
    var green: Double 
    var blue: Double
}
var color1 = Color1()
color1 = Color1(red: 1.0, green: 1.0, blue: 1.0)  // 자동구현
  • ⭐️ 개발자가 직접적으로 생성자를 구현하면, 멤버와이즈 이니셜라이저가 자동으로 제공되지 않음
    (멤버와이즈 이니셜라이저는 편의적 기능일 뿐)

✏️ 초기화 메서드 (생성자)

  • 함수의 구현이 특별한 키워드인 init 으로 명명됨
  • 인스턴스를 생성 과정: 저장 속성에 대한 초기값을 설정하여 사용가능한 상태가 되는 것
  • 생성자 메서드 실행의 목적은, 모든 저장 속성 초기화를 통한 인스턴스 생성
    (즉, 생성자 실행의 종료시점에는 모든 저장 속성에 값이 저장되어 있어야 함)
  • 클래스, 구조체, 열거형을 실제로 사용하기 위해 인스턴스를 찍어내는 과정
  • 생성자 실행 시, 메모리 내에 실제 인스턴스를 생성하는 방법을
✨ 오버로딩 지원
 - 다양한 파라미터 조합으로 생성자를 여러 개 구현가능
   (여러가지 방식으로 인스턴스를 생성하는 방법을 제공하는 것)
 ✨ 생성자 직접 구현하지 않으면
  🚨 (사용자 정의 (직접구현)가 일단 원칙, 개발자 의도가 우선)
  (1) 모든 저장 속성에 기본값(또는 옵셔널 타입) 전제
      - 클래스 : 기본 생성자 init() 제공
      - 구조체 : 멤버와이즈 이니셜라이저 기본 제공

📓 생성자

1) 지정생성자 (Designated)

  • 구조체 / 클래스 / (열거형)
    init(name: String, weight: Double) {
       self.name = name
       self.weight = weight
 - init(...) 형태를 가지는 생성자
 - 지정생성자는 모든 저장 속성을 초기화해야함
   (저장속성의 선언과 동시에 값을 저장하거나, 저장 속성을 옵셔널로 선언하는 것도 가능)
 - 오버로딩이 가능하므로, 다양한 파라미터 조합으로 지정생성자 구현 가능
 - (따로 지정하지 않아도) 모든 저장 속성이 초기화 되는 경우, 기본 생성자 자동 제공 ➡️ init()
 - 생성자를 1개이상 구현하면 기본 생성자를 제공하지 않음

2) 편의생성자 (Convenience) - 상속과 관련

  • 클래스
convenience init() {
    self.init( ... )
}
  • 지정생성자보다 적은 갯수의 파라미터로 보다 편리하게 생성하기 위한 서브개념의 생성자
  • 편의생성자는 지정생성자에 의존 및 호출 (지정생성자가 저장속성 초기화)
  • 초기화 과정을 간편하게 제공하기 위함
    - 실질적으로 가능한 지정생성자의 갯수를 줄이고,
    편의생성자에서 지정생성자 호출하도록 하는 것이 바람직
  • 상속했을 때, 편의생성자의 경우 서브클래스에서 재정의를 못함 (하위에서 호출불가가 원칙)
  • 편의생성자는 다른 편의생성자를 호출하거나, 지정생성자를 호출해야함 (궁극적으로는 지정생성자를 호출)
     ✨ 서브클래스의 편의생성자는 자기 단계의 지정생성자를 반드시 호출해야 하고, 
        그 지정생성자는 슈퍼클래스의(상위의) 지정생성자를 호출해야 한다
      ➡️ 이것이 바로 상속관계에서 생성자 위임 규칙
           ✏️ 1) 델리게이트 업 : 지정생성자는 슈퍼클래스의 지정생성자를 반드시 호출해야한다
             2) 델리게이트 어크로스 : 편의생성자는 궁극적으로 지정생성자호출해야한다 ~ 
 ✔️ 메모리구조 : 서브부터 데이터 저장 (주교재 446)

✏️ 생성자의 상속/재정의

  • 지정생성자/편의생성자 상속과 재정의 규칙
    * 생성자
  • 생성자는 기본적으로 상속되지 않고 재정의 원칙
    (생성자는 모든 저장 속성을 초기화하는 도구이기 때문에, 서브클래스에 최적화 되어있지 않음)
   - ⭐️ 원칙: 1)상위 지정생성자와  
            2)현재 단계의 저장속성을 고려하여 구현

3) 필수생성자 (Required) _ 상속과 관련

class Dog {
    var name: String
    var weight: Double
    ...
    required init() {
        self.name = "강아지"
        self.weight = 10.0
    }
    ...
}
  • 클래스의 생성자 앞에 required(필수의) 키워드를 붙이면 하위클래스에서
    반드시 해당 생성자를 구현해야 함
  • 해당 생성자의 의미(파라미터 이름 및 타입이 동일한)
  • 하위 클래스에서 필수 생성자 구현 시, overrie(재정의) 키워드 없이 required(필수의)
    키워드만 붙이면 됨
  • ⭐️ 필수생성자 자동상속 조건 : 다른 지정 생성자를 구현 안하면,
    자동으로 필수 생성자 상속됨
  • 애플이 미리 만들어놓은 프레임워크에는 필수 생성자가 있는 경우가 많기에 알고 있어야함!
    ex) UIView

4) 실패가능 생성자 (Failable)

class Dog {
    var name: String
    ...
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
    ...
}
  • 인스턴스 생성 시, 실패가능성을 가진 생성자
    ➡️ 실패가 불가능하게 만들어서, 아예 에러가 나고 앱이 완전히 꺼지는 가능성보다는
    실패가능 가능성 생성자를 정의하고 그에 맞는 예외 처리를 하는 것이 더 올바른 방법
  • 인스턴스 생성 실패 시 nil을 리턴한다
  • 생성자에 ?를 붙여서, init?(...) 으로 정의
    (다만, 오버로딩으로 인한 구분이 안되므로, 해당 이름을 가진 생성자는 유일한 생성자여야 함
    실패여부 알 수 없기 때문)
    1) 동일 단계 / 상속 관계에서의 호출
    : 실패불가능 생성자는 다른 실패가능 생성자를 호출 불가능
    2) 상속관계에서 재정의
    - (상위)init? ➡️ (하위)init (O)   _ 강제 언래핑 활용 가능
    - (상위)init ➡️ (하위)init (X)  
    - init! 생성자는 init? 과 유사하게 취급하면 됨

5) 소멸자 (Deinitializer)

class Dog {
   deinit {
       print("객체의 소멸")
   }
}
  • 생성자와 반대되는 개념
  • 메모리가 사라질 때 호출됨, 파라미터도 없음
  • 인스턴스 해제 시, 정리가 필요한 내용을 정의
  • 클래스에는 최대 1개의 소멸자가 존재
  • 인스턴스가 메모리에서 제거되기 직전에 자동으로 호출되는 메서드 부분
  • 소멸자는 클래스에만 존재 (힙영역에 저장이 되고, 메모리 관리를 해주어야 하기 때문에)

예시

class Aclass {
    var x = 0
    var y = 0
    
    deinit {
        print("인스턴스의 소멸 시점")
    }
}

var a: Aclass? = Aclass()
a = nil // "인스턴스의 소멸 시점"
// 메모리에서 없어질 때 자동 출력 . 실제로 프린트로 많이 구현
// 왜 필요할까? : 특별한 작업을 수행중일 경우 정리가 필요한 상황 등 ..


총정리

0개의 댓글