[Swift] Initialization


Initialization


초기화

  • 초기화는 클래스, 구조체, 열거형의 인스턴스를 생성하는 과정
  • 저장속성에 대한 초기값을 설정하여 인스턴스를 사용가능한 상태로 만드는 것
    (열거형은 저장속성이 존재하지 않으므로, case중에 한가지를 선택 및 생성)
  • 생성자 메서드 실행의 목적은, 모든 저장속성 초기화를 통한 인스턴스 생성
    생성자 실행의 종료시점에는 모든 저장 속성에 값이 저장되어 있어야 함
  • 이니셜라이저의 실행이 완료되었을 때, 인스턴스의 모든 저장속성이 초기값을 가지는 것이 생성자의 역할
    • 생성자의 반대개념인 소멸자
      인스턴스가 해제되기 전에 해야할 기능을 정의하는 부분
class Color {
    //let red, green, blue: Double    // 동일한 타입일때, 한줄에 작성가능
    let red: Double
    let green: Double
    let blue: Double
    
    // 생성자도 오버로딩(Overloading)을 지원 (파리미터의 수, 아규먼트 레이블, 자료형으로 구분)
    // 오버로딩을 통해 파라미터 활용의 자유도가 높아짐
    
    init() {  // "init()" -> 기본 생성자. 저장 속성의 기본값을 설정하면 "자동" 구현이 제공됨
        red = 0.0
        green = 0.0
        blue = 0.0
    }

    init(white: Double) {
        red = white
        green = white
        blue = white
    }

    init(red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }
}

var color = Color()   // 기본 생성자 호출. 결국 Color()는 생성자를 호출하는 것임 (메서드 호출 문법과 형태 동일)
//var color2 = Color.init()
//color = Color(white: 0.0)
//color = Color(red: 0.0, green: 0.0, blue: 0.0)
  • 초기화 방법 (저장속성이 초기값 가져야 함)
    1. 저장 속성의 선언과 동시에 값을 저장
    2. 저장 속성을 옵셔널으로 선언 (초기값이 없어도 nil로 초기화 됨)
    3. 생성자에서 값을 초기화
  • 생성자를 구현하지 않으면
    1. 모든 저장속성에 기본값 (또는 옵셔널 타입) 전제
      • 클래스 : 기본 생성자 init() 제공 (초기화 방법 필요)
      • 구조체 : 기본 생성자 init() 제공 + 멤버와이즈 이니셜라이저 기본 제공
    2. (일부) 저장속성에 기본값 (또는 옵셔널 타입) 전제
      • 클래스 : 원칙적으로 일부 값만 가지고 생성자 구현하지 않는 방법이 존재하지 않음
      • 구조체 : 멤버와이즈 이니셜라이저 기본 재공

멤버와이즈 이니셜라이저 (memberwise initializer)

  • 컴파일러는 기본 생성자를 자동으로 생성함 init()
    • 이니셜라이저를 구현하면, 기본 생성자를 자동으로 생성하지 않음
  • 구조체는 저장 속성들이 기본값을 가지고 있더라도 추가적으로 멤버와이즈 이니셜라이저를 자동으로 제공
    • 개발자가 직접 생성자를 구현하면 멤버와이즈 이니셜라이저가 자동으로 제공되지 않음

생성자

구조체의 생성자

struct Color {
    let red, green, blue: Double
    
    init() {      // 구조체는 다른 생성자를 호출하는 방식도 가능
        self.init(red: 0.0, green: 0.0, blue: 0.0)
    }

    init(white: Double) {
        self.init(red: white, green: white, blue: white)
    }
    
    init(red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }
}

// 값타입(구조체)의 경우 자체 지정생성자 작성할 때 생성자 내에서 self.init()을 사용하여 다른 이니셜라이저를 호출하도록 할 수 있음

클래스의 생성자

지정 생성자 (Designated Initializers) & 편의 생성자 (Convenience Initializers)

class Color {
    let red, green, blue: Double
    
    // convenience 키워드를 통해 편의 생성자 생성
    convenience init() {
        self.init(red: 0.0, green: 0.0, blue: 0.0)
    }

    convenience init(white: Double) {
        self.init(red: white, green: white, blue: white)
    }
    
    init(red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }
}

// 이미 모든 속성을 초기화하는 지정 생성자가 있다면 모든 속성을 초기화 하지 않는 경우 편의 생성자로 구현 권장
  • 편의 생성자는 편리하게 생성하기 위한 메인이 아닌 서브 생성자라고 보면 됨
    메인 지정 생성자에 의존하는 방식 (지정 생성자 호출)
    • 지정 생성자는 모든 속성을 초기화 해야함
    • 편의 생성자는 모든 속성을 초기화 할 필요가 없음
  • 클래스는 상속을 지원하므로 변수가 여러가지고, 여러 지정 생성자를 지원했을 때 상속관계에서 개발자가 실수할 수 있는 여러가지 가능성이 있음
  • 따라서 초기화 과정을 조금 간편하게 만들고, 상속관계에서 개발자가 실수할 수 있는 여러 가능성을 배제하기 위한 생성자
  • 반대로 말하면 모든 속성을 초기화 하지 않는다면, 편의 생성자로 만드는 것이 복잡도나 실수를 줄일 수 있는 방안이 될 수 있음
  • 결국, 가능한 생성자의 중복을 없애고 다른 지정 생성자를 호출하는 패턴으로 구현해야 함

생성자 위임 규칙 (Initializer Delegation)

모든 저장 속성 값이 초기화 되어야지만 인스턴스 사용 가능

출처 : https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
클래스 타입을 위한 이니셜라이저 위임
  1. 델리게이트 업 (Delegate up)
    • 서브클래스의 지정 생성자는 슈퍼클래스의 지정 생성자를 반드시 호출해야 함
    • 하위 클래스의 저장 속성을 초기화하고 상위 클래스의 저장 속성을 초기화하는 과정
  2. 델리게이트 어크로스 (Delegate across)
    • 편의 생성자는 동일한 클래스에서 다른 편의 생성자 또는 지정 생성자를 호출해야하고 궁극적으로는 지정 생성자를 호출해야 함
    • 궁극적으로 지정 생성자만이 해당 단계의 모든 저장값을 초기화 함

    -> 인스턴스 메모리 생성에 대한 규칙을 지키지 않으면 인스턴스가 올바르게 초기화 되지 않음

메모리 구조에 따른 상속 관계에서 생성자 호출 과정

  • 인스턴스의 모든 저장 속성이 초기값을 가지면 완전히 초기화 된 것
  • 각 단계에서 선언된 저장 속성은 각 해당 단계에서 초기값을 가져야 하고 그러고 나서 슈퍼 클래스로 생성 위임(델리게이트 업)이 일어나야 함

    1. 해당 클래스에서 선언한 모든 저장 속성에 값이 있는지 확인하여 메모리 초기화
    2. 상위 지정 생성자로 델리게이트 업 하여 해당 단계의 모든 저장 속성의 초기화
    3. 최종적으로 기본이 되는 클래스가 모든 저장 속성에 값이 있는지 확인
    4. 저장 속성이 완전히 초기화 된 것으로 간주되어 인스턴스가 생성 완료
class Person {
    var name: String
    var email: String

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

class Student: Person {
    // name
    // email
    var grade
    var gpa

    init(grade: Int, gpa: Double) {
        self.grade = grade
        self.gpa = gpa
    }

    convenience init() {
        self.init(grade: 1, gpa: 0.0)
    }
}
  1. 메모리(코드, 데이터, 힙, 스택)의 데이터 프레임에 슈퍼 클래스(Person)와 서브 클래스(Student) 생성
  2. 스택 영역에 서브 클래스 스택 프레임 생성
  3. 스택 영역에 서브 클래스의 편의 생성자 convience init() 스택 프레임 생성
  4. 델리게이트 어크로스를 통해 서브클래스의 지정 생성자 init() 호출
  5. 스택 영역에 서브 클래스의 지정 생성자 init() 스택 프레임 생성
  6. 힙 영역의 빈 메모리 공간에 서브 클래스의 프로퍼티 grade, gpa를 포함한 인스턴스 생성
  7. 서브 클래스의 지정 생성자가 델리게이트 업을 통해 슈퍼 클래스의 지정 생성자 호출
  8. 스택 프레임에 슈퍼 클래스의 지정 생성자 Super.init() 스택 프레임 생성
  9. 힙 영역에 생성된 인스턴스에 슈퍼 클래스의 프로퍼티 name, email 추가
  10. 스택 영역에서 스택 프레임을 차례대로 제거
    • Super.init() -> init() -> convience init()
  11. 마지막으로 스택 영역의 클래스가 힙 스택의 주소를 가리킴
  • 이런 동적 할당 과정을 거치게되어 힙 영역을 스캔하는 과정이 포함되어 클래스가 구조체보다 느림
  • 스위프트는 하위의 메모리를 먼저 찍어낸 뒤 그 다음으로 상위로 가서 찍어냄 -> 자바와는 반대

생성자의 상속 / 재정의

  • 하위 클래스는 기본적으로 상위 클래스 생성자를 상속하지 않고, 재정의 하는 것이 원칙
    • 올바르게 초기화되지 않을 수 있는 가능성은 배제 - 하위클래스에 최적화가 안되어 있음
    • 상위 클래스와 동일한 모든 지정 생성자가 하위 클래스에 존재할 때 (안전한 경우에) 상위 클래스의 생성자가 자동 상속됨
  • 생성자의 재정의
    • 동일한 이름을 가진 생성자를 구현하는 것
    • 하위 클래스의 커스텀 생성자 구현 전에 상위 클래스의 재정의 생성자를 작성해야 실수하지 않음

생성자 상속 시 구현 규칙

  • 기본 원칙
    • 상위의 지정 생성자현재단계의 저장 속성을 고려해서 구현
  1. 상위 생성자에 대한 고려
    • 상위에 어떤 지정 생성자가 존재하는지
    • 상위 지정 생성자
      • 하위 클래스에서 지정 생성자로 구현
      • 하위 클래스에서 편의 생성자로 구현 가능
      • 반드시 재정의하지 않아도 됨
    • 상위 편의 생성자
      • 호출 불가가 원칙이기 때문에 재정의 제공 하지 않음
        (만약 동일한 이름으로 구현했다면 그냥 새로 정의한 개념)
  2. 현재 단계의 생성자 구현
    • 지정 생성자 내에서 나의 모든 저장 속성 초기화, 상위 클래스의 지정 생성자 호출
    • 편의 생성자 내에서 현재 클래스의 지정 생성자를 호출해야 함
      (결론적으로 지정 생성자만 모든 저장 속성을 초기화 가능)
class Aclass {
    var x = 0
    // init() {}                // 기본 생성자가 자동으로 제공
}

class Bclass: Aclass {
    var y: Int

    // 1. 상위의 지정생성자 고려
    // 상위에 동일한 이름이 있으므로 재정의 해야함

    // [선택 1] 지정생성자로 재정의
    // 상위 클래스와 이름이 동일한 생성자 구현은 재정의만 가능 (지정 생성자로 구현)
    override init() {   
        self.y = 0
        super.init()
    }
    
    // [선택 2] 하위클래스에서 편의생성자로 구현
    // 상위 클래스와 이름이 동일한 생성자 구현은 재정의만 가능 (지정 생성자 필요)
    override convenience init() {
        self.init(y: 0)
    }
    
    // [선택 3] 재정의 하지 않을 수도 있음 (상속안함)
    
    // 2. 현재 단계의 생성자 구현
    init(y: Int) {
        self.y = y
        super.init()
    }

}

class Cclass: Bclass {
    var z: Int

    // 상위 클래스와 이름이 동일한 생성자 구현(올바른 재정의)
    override init() {
        self.z = 0
        super.init()  // [예외] 상위 구현의 기본 init() 만 있는 경우 생략가능 (암시적 요청)
    }
    
    init(z: Int) {
        self.z = z
        super.init()  // [예외] 상위 구현의 기본 init() 만 있는 경우 생략가능 (암시적 요청)
    }
    
}

[예외] 생성자 자동 상속

기본적으로 초기화의 실패 가능성을 배제 시 자동 상속이 된다.

  • 지정 생성자의 자동 상속
    • 저장 속성 기본값 설정 및 어떠한 재정의도 하지 않았을 경우
  • 편의 생성자의 자동 상속
    • 상위 지정 생성자를 모두 상속하는 경우
      • 상위의 지정 생성자를 모두 자동 상속하는 경우
      • 상위의 지정 생성자를 모두 재정의 하는 경우

    -> 결국 모든 지정 생성자를 상속하는 상황이 되면 편의 생성자는 자동으로 상속 됨

필수 생성자 (Required Initializers)

  • 클래스의 생성자 앞에 required 키워드를 붙이면 하위 클래스 에서도 반드시 해당 생성자를 구현해야 함
  • 하위 클래스에서 필수 생성자 구현 시 override 키워드 없이 required 키워드만 붙이면 됨
  • 필수 생성자 자동 상속 조건
    • 다른 지정 생성자를 구현하지 않으면, 자동으로 필수 생성자가 상속됨

실패 가능 생성자 (Failable Initializers)

  • 인스턴스 생성시, 생성에 실패할 수도 있는 가능성을 가진 생성자 (클래스, 구조체, 열거형 모두 가능)
    • 실패 가능 생성자를 통해 예외 처리를 할 수 있음
  • 인스턴스 생성 실패시 nil을 리턴
  • 생성자에 ?를 붙여서 init?()라고 정의하면 실패 가능 생성자로 정의
    • 오버로딩으로 실패 가능, 불가능을 구분 지을 수 없으므로, 동일한 파라미터를 가진 생성자는 유일해야함

실패 불가능 생성자는 다른 실패가능 생성자를 호출 불가능

  • 동일 단계 (델리케이트 어크로스)
    • 실패 가능 생성자 -> 실패 불가능 생성자 (호출/위임 가능)
    • 실패 불가능 생성자 -> 실패 가능 생성자 (호출/위임 불가능)
  • 상속 관계 (델리케이트 업)
    • (상위) 실패 가능 생성자 <- (하위) 실패 불가능 (호출/위임 불가능)
    • (상위) 실패 불가능 생성자 <- (하위) 실패 가능 (호출/위임 가능)
  • 두 경우 모두 초기화 실패를 유발하는 다른 생성자에 위임하면 전체 초기화 프로세스가 즉시 실패하고 더 이상 초기화 코드가 실행되지 않음

  • init! 키워드를 통해 강제 언래핑을 활용하여 호출/위임이 가능하게 만들어 줄 수 있음

소멸자 (Deinitializer)

class Aclass {
    var x = 0
    
    deinit {  // 소멸자 포맷
        print("인스턴스의 소멸 시점")
    }
}
  • 인스턴스가 메모리에서해제되기 직전 필요한 내용을 구현하는 메서드
  • 클래스 정의 시 클래스에는 1개의 소멸자 정의 가능 (한개만 가능)
  • 소멸자는 파라미터(매개 변수)를 사용하지 않음
  • 소멸자(초기화 해제) 작동 방식
    • Swift는 클래스의 인스턴스(객체)를 자동 참조 계산(ARC) 방식을 통해 메모리 관리
    • 일반적인 경우(강한 순환 참조를 제외)에는 메모리에서 해제될 때 수동으로 관리를 수행할 필요가 없음
    • 인스턴스에서 파일을 열고 일부 데이터를 쓰는 등의 특별한 작업을 수행중인 경우, 몇가지 추가 정리를 위해 직접 수행해야 할 수 있음
    • 클래스 인스턴스가 할당 해제되기 전에 파일을 닫아야 파일에 손상이 가지 않음
  • 소멸자에서는 인스턴스의 모든 속성에 엑세스 할 수 있으며 해당 속성을 기반으로 동작을 수정할 수 있음
    (ex. 닫아야하는 파일의 이름 조회)
  • 상속이 있는 경우
    • 상위 클래스 소멸자는 해당 하위 클래스에 의해 상속됨
    • 상위 클래스 소멸자는 하위 클래스 소멸자의 실행이 끝날 때 자동으로 호출됨
    • 상위 클래스 소멸자는 하위 클래스가 자체적인 소멸자를 제공하지 않더라도 항상 호출됨
  • 소멸자는 클래스에만 존재
    • 생성자는 필요한 만큼 구현할 수 있지만 소멸자는 하나만 구현 가능
    • 소멸자는 직접 호출 할 수 없고 인스턴스(객체)가 메모리에서 제거되기 직전에 자동으로 호출


 구조체 (Struct)클래스 (Class)
지정 생성자
(Desinated)
init() {}
(생성자 구현 안할 시 기본 생성자 + Memberwise 생성자 기본 제공)
init(파라미터) {}
init() {}
(생성자 구현 안할 시 기본 생성자 기본 제공)
init(파라미터) {}
편의 생성자
(Convenience)
Xconvenience init(파라미터) {}
(상속과 관련)
필수 생성자
(Required)
Xrequired init(파라미터) {}
(상속과 관련)
실패 가능 생성자
(Failable)
init?(파라미터) {}
init!(파라미터) {}
init?(파라미터) {}
init!(파라미터) {}
소멸자
(Deinitializers)
Xdeinit {}



참고

Ellen Swift Class






© 2021. by hminkim