아요 개발 일기

[Swift] 생성자(initializer) 본문

Swift

[Swift] 생성자(initializer)

소진이 2023. 1. 29. 15:19

생성자란?

  • 인스턴스가 생성될 때의 형식과 할 일을 정의한다고 하여 생성자라고 부릅니다.
  • 클래스가 객체화 될 때 자동으로 실행되는 메소드를 지칭합니다. ( 객체의 인스턴스를 만드는 메소드)
  • 스위프트 초기화 혹은 생성자는 init 이라는 키워드의 메소드가 담당합니다.
  • init은 구조체와 클래스 모두에서 지원되는 기능입니다.
  • 일반 메소드 정의와 다르게 func 라는 키워드가 필요 없습니다.

생성자는 init() 형태로 작성되며, 필요하면 argument를 추가해서 작성할 수도 있습니다.


초기화 메소드 init() 형식

init(<매개변수> : <타입>, <매개변수> : <타입>, ... ) {

	1. 매개변수의 초기화
	2. 인스턴스 생성 시 기타 처리할 내용
    
}

 

초기화 구문 = 초기화 메소드


초기화 메소드는 대표적인 특성이 아래와 같이 있습니다.

 

  1. 초기화 메소드의 이름은 init으로 통일된다
  2. 매개변수의 개수, 이름, 타입은 임의로 정의할 수 있다. ( 인스턴스 생성 과정에서 넣어야 하는 인자값의 순서와 형식이 결정)
  3. 매개변수의 이름과 개수, 타입이 서로 다른 여러 개의 초기화 메소드를 정의할 수 있다. ( 메소드의 일종이므로 오버라이딩할 수 있음)
  4. 정의된 초기화 메소드는 직접 호출되기도 하지만, 대부분 인스턴스 생성 시 간접적으로 호출된다.

초기화 메소드의 정의 방법과 호출 방법을 코드로 살펴 볼까요?

struct Resolution {
    var width = 0
    var height = 0

    // 초기화 메소드 : Width를 인자값으로 받음
    init(width: Int) {
        self.width = width
    }
}

class VideoMode {

    var resolution = Resolution(width: 2048)
    var interlaced = false
    var frameRate = 0.0
    var name: String?

    // 초기화 메소드 : interlaced, frameRate 두 개의 인자값을 받음
    init(interlaced: Bool, frameRate: Double) {
        self.interlaced = interlaced
        self.frameRate = frameRate
    }
}

먼저, width초기화 메소드가 정의되어있는 Resolution 구조체와

interlaced,frameRate  초기화 메소드가 정의되어있는 VideoMode 클래스를 정의 하였습니다.

 

 

이제 각각의 인스턴스를 생성해 볼까요?

방법 1


// Resolution 구조체에 대한 인스턴스 생성
let resolution = Resolution.init(width: 4096)

// VideoMode 클래스에 대한 인스턴스를 생성
let videoMode = VideoMode.init(interlaced: true, frameRate: 40.0)


방법 2


// Resolution 구조체에 대한 인스턴스를 생성
let resolution = Resolution(width: 4096)

// VideoMode 클래스에 대한 인스턴스를 생성
let videoMode = videoMode(interlaced: true, frameRate: 40.0)

 

방법1 처럼 init 메소드를 호출해 주는 방법이 있지만, 방법2와 같이 init을 생략한 후 호출도 가능합니다.

 

 

아래 코드는 서로 다른 여러 개의 init 메소드를 정의하고 호출 및 할당하는 예시 입니다.

class VideoMode {
    
    var resolution = Resolution(width: 4096)
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    
    
    // 초기화될 때 name 인자값만 받는 init 구문
    init(name: String) {
        self.name = name
    }
    
    // 초기화될 때 interlaced 인자값만 받는 init 구문
    init(interlaced: Bool) {
        self.interlaced = interlaced
    }
    
    // 초기화될 때 interlace, frameRate 두 개의 인자값을 받는 init 구문
    init(interlaced: Bool, frameRate: Double) {
        self.interlaced = interlaced
        self.frameRate = frameRate
    }
    
    // 초기화될 때 interlace, frameRate, name 세 개의 인자값을 박는 Init rnans
    init(interlaced: Bool, frameRate: Double, name: String) {
        self.interlaced = interlaced
        self.frameRate = frameRate
        self.name = name
    }
}

// VideoMode 클래스에 대한 인스턴스를 생성하고 상수에 할당
let nameVideoMode = VideoMode(name: "홍길동")
let simpleVideoMode = VideoMode(interlaced: true)
let doubleVideoMode = VideoMode(interlaced: true, frameRate: 40.0)
let tripleVideoMode = VideoMode(interlaced: true, frameRate: 40.0, name: "홍길동")

 

 

init 코드를 작성하고나면, 아래 코드와 같이 기본 초기화 구문은 제공되지 않습니다.

 

class VideoMode {

    var resolution = Resolution(width: 4096)
    var interlaced = false
    var frameRate = 0.0
    var name: String?

    // 초기화 메소드 : interlaced, frameRate 두 개의 인자값을 받음
    init(name: String) {
        self.name = name
    }
}

//VideoMode 클래스에 대한 인스턴스를 생성하고 상수에 할당
let defaultVideoMode = VideoMode() // Error
let nameVideoMode = VideoMode(name: "홍길동")

 

그럼 기본 초기화 구문을 사용할 수 없는건가요??

 

사용 할 수 있습니다. 하지만, 아래와 코드와 같이 기본 init() 메소드를 꼭 정의해주어야 합니다.

 

class VideoMode {

    var resolution = Resolution(width: 4096)
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    
    // 기본 초기화 구문
    init() {
        
    }

    // 초기화 메소드 : interlaced, frameRate 두 개의 인자값을 받음
    init(name: String) {
        self.name = name
    }
}

//VideoMode 클래스에 대한 인스턴스를 생성하고 상수에 할당
let defaultVideoMode = VideoMode() 
let nameVideoMode = VideoMode(name: "홍길동")

 

생성자

2-phase Initialization 

클래스 생성시 두가지 단계

1. 모든 저장 프로퍼티는 모두 이니셜라이저 되야한다

 —> 자식 프로퍼티부터 이니셜라이저해야한다

2. 부모 클래스까지 다 이니셜라이저하고나서야 프로퍼티를 사용 가능하다

—> 프로퍼티를 다 셋팅을하고나서 메소드를 사용할 수 있음

 

이런 규칙이 아주 중요함

규칙이 없다면?

프로퍼티가 셋팅도 안된 상태에서 인스턴스 메소드를 호출하게되고, 이렇게되면 우리가 원하는데로 동작하지 않고 버그가 생기게 됨

 

 

designated(주) vs convenience(부)

 

DI는 자신의 부모의 DI를 호출해야함

CI는 같은 클래스의 이니셜라이저를 꼭 하나 호출해야함

CI는 궁극적으로는 DI를 호출해야함