Swift - 생성자와 파괴자(Initialization and Deinitialization)

일반적으로 OOP에서는 생성자(Constructor)와 파괴자(Destructor) 라는 개념이 있다. 객체의 탄생과 사라지는 시점에 맞춰 시작과 끝에 호출되는 메소드로 설명되는데, 스위프트에서는 초기화(Initialization)와 마무리(Deinitialization)라는 개념의 용어를 사용한다. 용도는 어차피 비슷하다.

참고로 ‘마무리’ 라는 용어는 개인적으로 멋대로 부르는 것이고 정확히 뭐라 불러야 할지는 잘 모르겠다. :-)

Initialization

생성자(Constructor) 개념은 클래스가 객체화 될 때 자동으로 실행되는 메소드를 지칭한다. 스위프트의 초기화 혹은 생성자는 init 이라는 키워드의 메소드가 담당한다. 이 init 은 구조체(struct)와 클래스(class) 모두에서 지원되는 기능이다. Objective-C로 코딩을 해 봤다면 아주 익숙한 이름일 것이다.

init() 은 일반 메소드 정의와는 다르게 func 라는 키워드가 필요없다. 그러니 일반 메소드와는 다르다고 생각하자.
class SimpleClass {
    var value = 0
    
    init(v: Int) {
        self.value = v
    }
}
이름에 걸맡게 init 메소드는 초기화 작업 전반을 담당하면 된다.

init은 클래스나 구조체의 인스턴스를 생성 할 때의 넘기는 매개변수 구조에 따라 호출되는 것이 다르다.
class SimpleClass {
    var value = 0
    
    init(v: Int) {
        self.value = v
    }
    
    init() {
        self.value = -999
    }
}
두 개의 init 이 오버로드 되어있다. 하지만 매개변수가 다르다. 특히 하나는 매개변수가 존재하지 않는다. 이 매개변수 없는 init() 은 기본 초기화 메소드(Default Initialization)로 아래과 같이 그냥 인스턴스를 생성하면 호출된다.
var instance = SimpleClass()
물론 인자를 넣어서 다른 초기화 메소드를 이용하는 것도 가능하다.
var instance = SimpleClass(v: 50)
init() 이 다른 메소드와 다른점은 모든 매개변수에 별명이 붙는다는 점이다. 위 처럼 모든 매개변수 별명을 호출 시 적어줘야 한다. 하지만 메소드와 비슷하게 init도 별명 생략 기능을 이용 할 수 있다.
class SimpleClass {
    var value = 0
    
    init(_ v: Int) {
        self.value = v
    }
    
    init() {
        self.value = -999
    }
}

let a = SimpleClass(10)     // {value 10}
let b = SimpleClass()       // {value -999}
별명을 필요없다고 선언된 init이 오버로드된 경우도 잘 돌아간다.

상속 시 init도 그대로 상속된다. 또한 init을 오버라이드 할 때 super의 init을 호출하는 것으로 부모의 초기화 메소드를 그대로 이용 할 수 있다. 다만, init 은 오버라이드 시 override 를 표기할 필요가 없다.
class AnotherClass: SimpleClass {
    init() {
        super.init()
    }
}

var instance = AnotherClass()
// instance.value == -999
물론 super.init() 을 호출하지 않아도 위 예제는 문제는 없겠지만, 상속 시 부모의 생성자(초기화 메소드)를 호출하지 않으면 대게 문제가 발생할 여지가 높다. 기억해두자.

앞서 init() 은 오버로드가 가능하다고 설명했었다. 그런데 한 클래스의 init에서 자신의 다른 init을 호출하면 오류가 발생한다. 앞에서 본 SimpleClass를 다시 수정해보자.
class SimpleClass {
    var value = 0
    
    init(v: Int) {
        self.value = v
    }
    
    init() {
        self.init(v: -999)     // ERROR!
    }
}
기본 초기화 메소드인 init()을 수정했다. 의도는 이전과 동일한 결과를 내려고 하는 것인데, 문제는 self.init(v: Int) 를 호출하려는 과정이 오류로 표시된다. 스위프트의 또다른 제약인 셈이다.

물론 해법을 제공해주고 있다. convenience(편의)라는 이름의 키워드를 정의하면 가능하다.
class SimpleClass {
    var value = 0
    
    init(v: Int) {
        self.value = v
    }
    
    convenience init() {
        self.init(v: -999)
    }
}
이렇게 하면 기본 생성자인 init() 은 이제 init(v: Int) 초기화 메소드를 이용해 초기화를 진행 할 수 있게 된다.

convenience 키워드는 한 init이 다른 init을 호출하는 식으로 init이 여러 단계에 걸쳐서 처리(이른바 multi-step initialization)해야 할 때 필요한 키워드이다.

Deinitialization

기존 OOP의 파괴자(Destructor)와 비슷한 마무리 용도의 기능으로 deinit 이 있다. Objective-C의 dealloc 메소드와 비슷한 용도이다. 이름 만으로도 판단이 가능하겠지만 init의 반대 요소이다.

참고로 deinit은 클래스에서만 사용이 가능하다는 점에 주의하자.

deinit 은 메소드라 하기엔 좀 모양이 다르다. 앞서 살펴본 SimpleClass를 좀 뜯어고친 예제를 보자.
class SimpleClass {
    var value = 0
    
    deinit {
        self.value = 0
    }
}
deinit 은 매개변수를 가질 수 없다. 스위프트에서 파괴자는 메소드의 형태가 아니라는 말이다. 뭐 그래도 하는 역활은 파괴자 메소드와 별 차이가 없어보이긴 한다. 대게 프로그래머가 직접 파괴자를 호출하도록 코드를 짤 수 없기 때문에 대부분의 OOP용 언어에서 파괴자는 매개변수가 존재하지 않는다.

간략히 정리하자면, deinit은 클래스 인스턴스가 필요없어져 메모리에서 사라질 때 (즉 레퍼런스 카운트가 0이 될 때) 호출되는 기능이다. 주로 자신이 점찍어 놓은(retain) 다른 인스턴스의 메모리가 해제 될 수 있도록 조치(release) 해 주는 것이 목적이지만, 스위프트는 ARC기반 하에 있어서 이런 행위는 거의 필요없다.

아마도 스위프트에서 deinit 은 싱글턴 인스턴스 등이 자신의 인스턴스를 활용하고 있다면 해당 싱글턴 인스턴스에서 자신을 참조하지 않도록 처리하는데 주로 사용되지 않을까 생각된다. 물론 한가지 예일 뿐이지 그 사용 용도는 적재적소에 맞게 구현하면 된다.

[관련글] Swift - 클래스(Class) 훑어보기
[관련글] Swift - 구조체(Structure) 훑어보기
[관련글] Swift - 메소드(Method)

[돌아가기] 스위프트(Swift) 가이드

댓글

이 블로그의 인기 게시물

버전(Version)을 제대로 이해하기

소수점 제거 함수 삼총사 ceil(), floor(), round()