본문 바로가기

문법

[Swift] 인스턴스 생성과 소멸을 알아보자

주의 : 배우는 과정이기에  정확하지않는 정보가 있을 수 있습니다.

 

 

 

인스턴스 생성과 소멸
- 인스턴스 생성
- 프로퍼티 기본값
- 이니셜라이저 매개변수
- 옵셔널 프로퍼티 타입
- 상수 프로퍼티
- 초기화 위임
- 실패 가능한 이니셜라이저
- 인스턴스 소멸
- 포인트 정리
- 궁금증

 

 

인스턴스 생성과 소멸

구조체와 클래스 생성할 때는 지금까지 기본 이니셜라이저를 사용해 인스턴스를 생성했다.

초기화는 클래스나 구조체 또는 열거형의 인스턴스를 사용하기 위한 준비 과정이다. 초기화가 완료된 인스턴스는 사용 후 소멸시점이 오면 소멸합니다. 이번 장에서는 인스턴스를 생성하는 방법과 클래스의 인스턴스가 소멸할 때 어떤 프로세스가 진행되는지 알아보자. 클래스 인스턴스의 소멸 시점은 ARC에서, 클래스의 이니셜라이저와 이니셜라이저 상속에 관한부분은 상속에서 더 자세히 다룰 예정이다.

 

 

인스턴스 생성

초기화 과정은 새로운 인스턴스를 사용할 준비를 하기 위하여 저장 프로퍼티의 초깃값을 설정하는 등의 일을 한다. 이니셜라이저를 정의하면 초기화 과정을 직접 구현할 수 있다. 그렇게 구현된 이니셜라이저는 새로운 인스턴스를 생성할 수 있는 특별한 메서드가 된다. 스위프트의 이니셜라이저는 반환 값이 없다. 이니셜라이저의 역할은 그저 인스턴스의 첫 사용을 위해 초기화 하는 것 뿐이다.

 

이니셜라이저는 해당 타입의 새로운 인스턴스를 생성하기 위해 호출한다. 매개변수가 없는 기본 이니셜라이저의 모습이다. 이니셜라이저는 func키워드를 사용하지않고 오로지 init키워드를 사용하여 이니셜라이저 메서드임을 표현한다. init메서드는 클래스, 구조체, 열거형 등의 구현부 또는 해당타입의 익스텐션 구현부에 위치한다. 다만 클래스의 지정 이니셜라이저는 익스텐션에서 구현해 줄 수 없다.

 

클래스, 구조체, 열거형의 기본적인 형태의 이니셜라이저

class SomeClass {
    init() {
        //초기화할 때 필요한 코드
    }
}

struct SomeStruct {
    init() {
        // 초기화 할 때 필요한 코드
    }
}

enum SomeEnum {
    case someCase
    
    init() {
        // 열거형은 초기화할때 반드시 case중 하나가 되어야한다.
        self = .someCase
        // 초기화할 때 필요한 코드
    }
}

 

 

프로퍼티 기본값

 

구조체와 클래스의 인스턴스는 처음 생성할 때 옵셔널 저장 프로퍼티를 제외한 모든 저장 프로퍼티에 적절한 초기값을 할당해야한다.

이니셜라이저가 실행될 때 저장 프로퍼티에 적절한 초깃값을 할당할 수 있다. 초기화 후에 값이 확정되지 않은 저장 프로퍼티는 존재할 수 없습니다. 프로퍼티를 정의할 때 프로퍼티 기본값을 할당하면 이니셜라이저에서 따로 초깃값을 할당하지 않더라도 프로퍼티 기본값으로 저장 프로퍼티의 값이 초기화 된다. (옵셔널이 아닌이상 프로퍼티에 기본값을 할당하거나 초기값을 할당할 수 있도록 이니셜라이저를 생성해 주거나 둘 중 무조건 택1)

 

NOTE

초기화와 프로퍼티 감시자

이니셜라이저를 통해 초깃값을 할당하거나, 프로퍼티 기본값을 통해 처음의 저장 프로퍼티가 초기화 될때는 프로퍼티 감시자 메서드가 호출되지 않는다.

 

Area 구조체와 이니셜라이저

// Area 구조체와 이니셜라이저
struct Area {
    var squareMeter: Double
    
    init() {
        squareMeter = 0.0 // squareMeter의 초깃값 할당
    }
}

let room = Area()
print(room.squareMeter) // 0.0

 

Are구조체는 squareMeter라는 Double 타입의 저장 프로퍼티를 가지고 있다. init 이니셜라이저로 인스턴스를 초기화하며 squareMeter의 초깃값은 0.0이 된다. 앞서 설명했듯 이니셜라이저로 저장 프로퍼티에 초깃값을 설정하는 방식도 있지만, 프로퍼티를 정의할 때 프로퍼티에 기본값을 할당하는 방식을 사용할 수도 있다. 프로퍼티에 기본값을 할당하는 방법을 살펴보자.

 

 

프로퍼티 기본값 지정

// 프로퍼티 기본값 지정

struct Area2 {
    var squareMeter: Double = 0.0 //프로퍼티 기본값 할당
}

let room2 = Area()
print(room2.squareMeter) // 0.0

 

초기화 과정은 이니셜라이저의 매개변수, 옵셔널 프로퍼티, 상수 프로퍼티의 값 할당 등 프로그래머의 의도대로 구현할 수 있는 수 많은 패턴의 이니셜라이저가 있다.

 

 

이니셜라이저 매개변수

 

이니셜라이저 매개변수

struct Area3 {
    var squareMeter: Double
    
    init(fromPy py: Double) {                   // 첫번째 이니셜라이저
        squareMeter = py * 3.3058
    }
    
    init(fromSquareMeter squareMeter: Double) { // 두번째 이니셜라이저
        self.squareMeter = squareMeter
    }
    
    init(value: Double) {                       // 세번째 이니셜라이저
        squareMeter = value
    }
    
    init(_ value: Double) {                     // 네번째 이니셜라이저
        squareMeter = value
    }
    
    init?(optionalValue value: Double) {        // 다섯번째 이니셜라이저
        squareMeter = value
    }
}

let roomOne = Area3(fromPy: 15.0)
print(roomOne.squareMeter) //49.587

let roomTwo = Area3(fromSquareMeter: 33.06)
print(roomTwo.squareMeter) //33.06

let roomThree = Area3(value: 30.0)
print(roomThree.squareMeter) //30.0

let roomFour = Area3(optionalValue: 0.0)
print(roomFour?.squareMeter) //Optional(0.0)

 

두 종류의 이니셜라이저를 만들었다. 평수를 입력받아 제곱미터로 환산한 값을 squareMeter 프로퍼티에 할당하는 이니셜라이저와 제곱미터를 입력받아 그대로 squareMeter 프로퍼티에 할당하는 이니셜라이저이다. 이렇게 사용자 정의 이니셜라이저를 만들면 기존의 기본 이니셜라이저는 별도로 구현하지 않는 이상 사용할 수 없다.

 

두번째 이니셜라이저 init(fromSquareMeter squareMeter: Double) 에서는 self 프로퍼티를 사용(self.squareMeter) 하여 이니셜라이저의 전달인자 레이블인 squareMeter와 구분지었다.

 

세 번째 이니셜라이저에서는 따로 전달인자 레이블을 사용하지 않았다. 별다른 의미없는 value라는 이름의 매개변수가 있으므로 만약 자동으로 지정되는 전달인자 레이블 value가 필요하지 않다면 네 번째 이니셜라이저처럼 와일드 카드 식별자를 사용하여 전달인자 레이블을 없애주면 된다.

 

재미있는 예로 이니셜라이저가 다양하게 구현된 스위프트 기본 타입을 들 수 있었는데 스위프트 표준 라이브러리에서 스위프트의 기본타입들의 정의를 찾아보자

[https://developer.apple.com/documentation/swift/swift_standard_library]

 

 

옵셔널 프로퍼티 타입

초기화 과정에서 값을 초기화하지 않아도 되는, 즉 인스턴스가 사용되는 동안에 값을 꼭 갖지않아도 되는 저장 프로퍼티가 있다면 해당 프로퍼티를 옵셔널로 선언할 수 있다. 또는 초기화 과정에서 값을 지정해주기 어려운 경우 저장 프로퍼티를 옵셔널로 선언할 수도 있다. 옵셔널로 선언한 저장 프로퍼티는 초기화 과정에서 값을 할당해주지 않는다면 자동으로 nil이 할당된다. 코드로 확인해보자

 

Person 클래스

class Person {
    var name: String
    var age: Int?
    
    init(name: String) {
        self.name = name
    }
}

let quokka = Person(name: "쿼카")
print(quokka.name) // 쿼카
print(quokka.age) // nil

quokka.age = 25
print(quokka.age) //Optional(25)

quokka.name = "kangaru"
print(quokka.name)// kangaru

사람의 이름은 아는데 나이는 민감한 부분이므로 모를 수 있기때문에 age프로퍼티를 옵셔널로 선언했다. 아니셜라이저에서 특별히 초기화하지않았지만 자동으로 nil이 할당되어 있다. 나중에 나이를 알게되는 시점에서 제대로 된 값을 할당할 수 있다.

 

 

상수 프로퍼티

이름 프로퍼티(name)를 상수가 아닌 변수로 선언해준다면 "Eric"이라는 이름을 할당하고 난 후에 전혀 다른사람으로 변할 수 있다.

이런 상황을 방지하려면 name프로퍼티를 상수로 선언해야한다. 이때 고려해야할 점이 있는데 상수로 선언된 저장 프로퍼티는 인스턴스를 초기화하는 과정에서만 값을 할당할 수 있으며, 처음 할당된 이후로는 값을 변경할 수 없다. 이 점을 꼭 기억하자.

 

NOTE

상수 프로퍼티와 상속

클래스 인스턴스의 상수 프로퍼티는 프로퍼티가 정의된 클래스에서만 초기화할 수 있다. 해당 클래스를 상속받은 자식 클래스의 이니셜라이저에서는 부모클래스의 상수 프로퍼티 값을 초기화할 수 없다.

 

상수 프로퍼티 초기화

// 상수 프로퍼티의 초기화
class Person2 {
    let name: String
    var age: Int?
    
    init(name: String) {
        self.name = name
    }
}

let quokka2 = Person2(name: "쿼카")
//quokka2.name = "Eric" // 오류 발생 - Cannot assign to property: 'name' is a 'let' constant

 

 

기본 이니셜라이저와 멤버와이즈 이니셜라이저

 

이제까지 사용자 정의 이니셜라저에 대해 알아봤는데, 정작 기본 이니셜라이저에 대해서는 알아보진 못했다. 사용자 정의 이니셜라이저를 정의해주지 않으면 클래스나 구조체는 모든 프로퍼티에 기본값이 지정되어있다는 전제하에 기본 이니셜라이저를 사용한다. 기본 이니셜라이저는 프로퍼티 기본값으로 프로퍼티를 초기화해서 인스턴스를 생성한다. 즉, 기본 이니셜라이저는 저장 프로퍼티의 기본값이 모두 지정되어 있고, 동시에 사용자 정의 이니셜라이저가 정의되어 있지 않은 상태에서 제공된다.

 

저장 프로퍼티를 선언할 때 기본값을 지정해주지 않으면 이니셜라이저에서 초깃값을 설정해야한다. 그러나 프로퍼티 하나때문에 매번 이니셜라이저를 추가하거나 변경하는 일은 여간 귀찮은 일이 아니다. 때문에 구조체는 사용자 정의 이니셜라이저를 구현하지 않으면 프로퍼티의 이름으로 매개변수를 갖는 이니셜라이저인 멤버와이즈 이니셜라이저를 기본으로 제공한다. 그렇지만 클래스는 멤버와이즈 이니셜라이저를 지원하지 않는다.

 

구조체는 기본적으로 제공하는 멤버와이즈 이니셜라이절르 사용하여 인스턴스를 생성한다.

 

Point 구조체와 Size 구조체의 선언과 멤버와이즈 이니셜라이저의 사용

struct Point {
    var x: Double = 0.0
    var y: Double = 0.0
}

struct Size {
    var width: Double = 0.0
    var height: Double = 0.0
}

let point = Point(x: 0, y: 0)
let size = Size(width: 50.0, height: 50.0)

// 구조체의 저장 프로퍼티에 기본값이 있는 경우
// 필요한 매개변수만 사용하여 초기화할 수도 있습니다.
let somePoint = Point()
let someSize = Size(width: 50)
let anotherPoint = Point(y: 100)

앞서 언급했듯 클래스는 멤버와이즈 이니셜라이저를 지원하지 않으므로 멤버와이즈 이니셜라이저는 구조체만의 특권이다.

 

 

초기화 위임

 

 

값 타입인 구조체와 열거형은 코드의 중복을 피하기 위하여 이니셜라이저가 다른 이니셜라이저에게 일부 초기화를 위임하는 초기화 위임을 간단하게 구현할 수 있다. 하지만 클래스는 상속을 지원하는 터라 간단한 초기화 위임도 할 수 없다. 클래스의 초기화위임은 상속에서 자세히 알아볼 수 있다.

 

값 타입에서 이니셜라이저가 다른 이니셜라이저를 호출하려면 self.init을 사용한다. 당연히 self.init은 이니셜라이저 안에서만 사용할 수 있는데 self.init을 사용한다는 것 자체가 사용자 정의 이니셜라이저를 정의하고 있다는 뜻이다. 그런데 사용자 정의 이니셜라이저를 정의하면 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 사용할 수 없다고 했다. 따라서 초기화 위임을 하려면 최소 두개 이상의 사용자 정의 이니셜라이저를 정의해야한다.

 

NOTE

기본 이니셜라이저를 지키고 싶다면

사용자 정의 이니셜라이저를 정의할 때도 기본 이니셜라이저나 멤버와이즈 이니셜라이저를 사용하고 싶다면 익스텐션을 사용하여 사용자 정의 이니셜라이저를 구현하면 된다. 자세한건 익스텐션과 프로토콜 장에서 알아보자.

 

Student 열거형과 초기화 위임

enum Student {
    case element, middle, high
    case none

    //사용자 정의 이니셜라이저가 있는 경우, init() 메서드를 구현해주어야 기본 이니셜라이저를 사용할 수있다.
    init() {
        self = .none
    }
    
    init(koreanAge: Int) {               // 첫 번째 사용자 정의 이니셜라이저
        switch koreanAge {
        case 8...13:
            self = .element
        case 14...16:
            self = .middle
        case 17...19:
            self = .high
        default:
            self = .none
        }
    }
    
    init(bornAt: Int, currentYear: Int) { // 두 번째 사용자 정의 이니셜라이저
        self.init(koreanAge: currentYear - bornAt + 1)
    }
}

var younger = Student(koreanAge: 16)
print(younger) // middle

younger = Student(bornAt: 1998, currentYear: 2016)
print(younger) // high

 

열거형은 두 개의 사용자 정의 이니셜라이저가 있습니다. 첫 번째 사용자 정의 이니셜라이저는 나이를 전달 받아 나이에 맞는 학교를 case로 구분한 이니셜라이저를 초기화하고, 두 번째 사용자 정의 이니셜라이저는 태어난 해와 현재 연도를 전달받아 나이로 계산한 후 첫번째 이니셜라이저에 초기화 위임한다. 이렇게 초기화 위임 방법을 사용하면 코드를 중목으로 쓰지않고 효율적으로 여러 case의 이니셜라이저를 만들 수 있다.

 

실패 가능한 이니셜라이저

이니셜라이저를 통해 인스턴스를 초기화할 수 없는 여러가지 예외 상황이 있다. 대표적으로 이니셜라이저의 전달인자로 잘못된 값이나 적절치 못한 값이 전달되었을 때, 이니셜라이저는 인스턴스 초기화에 실패할 수 있다. 그 외에도 여러 이유로 인스턴스 초기화에 실패할 수 있다.

 

이니셜라이저를 정의할 때 이런 실패 가능성을 염두고 해기도 하는데, 이렇게 실패 가능성을 내포한 이니셜라이저를 실패 가능한 이니셜라이저라고 부릅니다. 실패 가능한 이니셜라이저는 실패했을때 nil을 반환해주므로 반환 타입이 옵셔널로 지정된다. 따라서 실패 가능한 이니셜라이저는 init 대신 init? 키워드를 사용한다.

 

NOTE

이니시셜라이저의 매개변수

실패하지 않는 이니셜라이저와 실패 가능한 이니셜라이저를 같은 이름과 같은 매개변수 타입을 갖도록 정의할 수 없다. 실패 가능한 이니셜라이저는 실제로 특정 값을 반환하지 않는다. 초기화를 실패했을 때는 return nil을 반대로 초기화에 성공했을 때는 return을 적어 초기화의 성공과 실패를 표현할 뿐, 실제 값을 반환하지 않는다.

 

Person3클래스는 이름이나 나이가 잘못 입력되면 실패할 수도 있다. 실패가능한 이니셜라이저를 사용하면 잘못된 전달인자를 받았을때 초기화하지 않을 수 있다.

 

실패 가능한 이니셜라이저

// 실패 가능한 이니셜라이저
class Person3 {
    let name: String
    var age: Int?
    
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
    
    init?(name: String, age: Int) {
        if name.isEmpty || age < 0 {
            return nil
        }
        self.name = name
        self.age = age
    }
}

let quokka3: Person3? = Person3(name: "quokka", age: 25)

if let person = quokka3 {
    print(person.name)
} else {
    print("Person wasn't initialzed")
}
// quokka

let chope = Person3(name: "chope", age: -10)

if let person = chope {
    print(person.name)
} else {
    print("Person wasn't initialzed")
}
// Person wasn't initialzed

let eric = Person3(name: "", age: 30) // 비어있다와 값이 없다의 구분법,  "" : nil

if let person = eric {
    print(person.name)
} else {
    print("Person wasn't initialzed")
}
// Person wasn't initialzed

실패 가능한 이니셜라이저는 구조체와 클래스에서도 유용하지만 특히 열거형에서 유용하게 사용할 수 있다. 특정 case에 맞지 않는 값이 들어오면 생성에 실패 할 수 있다. 혹은 rawValue로 초기화할 때, 잘못된 rawValue가 전달되어 들어온다면 열거형 인스턴스를 생성하지 못할 수 있다. 따라서 rawValue를 통한 이니셜라이저는 기본적으로 실패 가능한 이니셜라이저로 제공된다.

Student 열거형을 조금 수정한 코드이다.

 

열거형의 실패 가능한 이니셜라이저

enum Student2: String {
    case elementary = "초등학생"
    case middle = "중학생"
    case high = "고등학생"
    
    init?(koreanAge: Int) {
        switch koreanAge {
        case 8...13:
            self = .elementary
        case 14...16:
            self = .middle
        case 17...19:
            self = .high
        default:
            return nil
        }
    }
    
    init?(bornAt: Int, currentYear: Int) {
        self.init(koreanAge: currentYear - bornAt + 1)
    }
}

var younger2 = Student2(koreanAge: 19)
print(younger2?.rawValue) //

younger2 = Student2(bornAt: 1998, currentYear: 2020)
print(younger2?.rawValue) //

younger2 = Student2(rawValue: "대학생")
print(younger2?.rawValue) //

younger2 = Student2(rawValue: "고등학생")
print(younger2?.rawValue) //

 

함수를 사용한 프로퍼티 기본값 설정(이해도낮음)

 

만약 사용자 정의 연산을 통해 저장프로퍼티 기본값을 설정하고자 한다면 클로저나 함수를 사용하여 프로퍼티 기본값을 제공할 수 없다.

인스턴스를 초기화할 때 함수나 클로저가 호출되면서 연산 결괏값을 프로퍼티 기본값으로 제공해준다. 그렇기 때문에 클로저나 함수의

반환 타입은 프로퍼티의 타입과 일치해야한다.

 

만약 프로퍼티 기본값을 설정해주기 위해서 클로저를 사용한다면 클로저가 실행되는 시점은 초기화할 때 인스턴스의 다른 프로퍼티값이

설정되기 전이라는 것도 꼭 명심해야한다. 즉, 클로저 내부에서는 인스턴스의 다른 프로퍼티를 사용하여 연산할 수 없다는 것이다.

다른 프로퍼티에 기본값이 있다고 해도 안된다. 또한, 클로저 내부에서 self프로퍼티도 사용할 수 없으며, 인스턴스 메서드를 호출할 수도 없다.

 

typealias SomeType = Any
// 클로저를 통한 프로퍼티 기본값 설정
class SomeClass2 {
    let someProperty: SomeType = {
        // 새로운 인스턴스를 생성하고 사용자 정의 연산을 통한 후 반환해준다.
        // 반환되는 값의 타입은 SomeType과 같은 타입이어야 한다.
        return //someValue
    }()
}

클로저 뒤에 소괄호가 붙은 이유는 클로저를 실행하기 위해서입니다. 클로저 뒤에 소괄호가 붙어 클로저를 실행한 결괏값은 프로퍼티의 기본값이 됩니다. 만약 소괄호가 없다면 프로퍼티의 기본값은 클로저 그 자체가 됩니다. 우리가 의도했던 것과는 전혀 다른 의미가 되는것이죠.

 

 

클로저를 통한 student 프로퍼티 기본값 설정

// 클로저를 통한 student 프로퍼티 기본값 설정
struct Student3 {
    var name: String?
    var number: Int?
}

class SchoolClass {
    var students: [Student3] {
        // 새로운 인스턴스를 생성하고 사용자 정의 연산을 통한 후 반환해준다.
        // 변환되는 값의 타입은 [Student] 타입이어야 한다.
        var arr = [Student3]()
        
        for num in 1...15 {
            var student = Student3(name: nil, number: num)
            arr.append(student)
        }
        
        return arr
    }
}

let schoolClass = SchoolClass()
print(schoolClass.students.count) // 15

students프로퍼티는 Student3 구조체의 인스턴스 요소로 값는 Array타입이다. SchoolClass클래스의 인스턴스를 초기화하면 students 프로퍼티의 기본값을 제공하기위해 클로저가 동작하고 1번부터 15번까지의 학생을 생성하여 배열에 할당한다. myClass인스턴스는 생성되자마자 students 프로퍼티에 15명의 학생이 있는 상태가 되는것이다.

 

TIP

IOS에서의 활용

스위프트 언어와는 크게 관계가 없지만 iOS의 UI등을 구성할 때, UI컴포넌트를 클래스의 프로퍼티로 구현하고 UI 컴포넌트의 생성과 동시에 컴포넌트의 프로퍼티를 기본적으로 설정할때 유용하게 사용할 수 있다.

뭔소릴까..

지금까지 각 인스턴스를 초기화하는 방법에 대해 알아보았다. 이처럼 다양한 초기화 기법을 통해 인스턴스를 의도한 대로 구현할 수 있다.

 

 

인스턴스 소멸

 

클래스의 인스턴스는 디이니셜라이저(Deinitializer)를 구현할 수 있다. 디니이셜라이저는 이니셜라이저와 반대 역할을 한다. 즉, 메모리에서 해제되기 직전 클래스 인스턴스와 관련하여 원하는 정리 직업을 구현할 수 있다. 디이니셜라이저는 클래스의 인스턴스가 메모리에서 소멸되기 바로 직전에 호출된다. deinit키워드를 사용하여 디이니셜라이저를 구현하면 자동으로 호출된다. 디이니셜라이저는 클래스의 인스턴스에만 구현할 수 있다.

 

스위프트는 인스턴스가 더 이상 필요하지 않으면 자동으로 메모리에서 소멸*시킨다. 인스턴스 대부분은 소멸시킬 때 디이니셜라이저를 사용해 별도의 메모리 관리 작업을 할 필요는 없다. 그렇지만 예를들어 인스턴스 내부에서 파일을 불러와 열어보는 등의 외부자원을 사용했다면 인스턴스를 소멸하기 직전에 파일을 다시 저장하고 닫아주는 등의 부가 작업을 해야합니다. 또는 인스턴스를 메모리에서 소멸하기 직전에 인스턴스에 저장되어있던 데이터를 디스크에 파일로 저장해줘야하는 경우도 있을 수 있다. 그런 경우에 디이니셜라이저는 굉장히 유용하게 사용할 수 있다.

 

클래스에는 디이니셜라이저를 단 하나만 구현할 수 있다. 디이니셜라이저는 이니셜라이저와는 다르게 매개변수를 갖지 않으며, 소괄호도 적어주지 않는다. 또 자동으로 호출되기 때문에 별도의 코드로 호출할 수 있다.

 

디이니셜라이저는 인스턴스를 소멸하기 직전에 호출되므로 인스턴스의 모든 프로퍼티에 접근할 수 있으며 (큰 의미는 없을 수 있지만)프로퍼티의 값을 변경할 수도 있다.

 

디이니셜라이저 구현(이해도 낮음)

// 디이니셜라이저 구현
class SomeClass3 {
    deinit {
        print("Instance will be deallocated immediately")
    }
}

var instance: SomeClass3? = SomeClass3()
instance = nil // Instance will be deallocated immediately

// FileManager 클래스의 디이니셜라이저 활용
class FileManager {
    var fileName: String
    
    init(fileName: String) {
        self.fileName = fileName
    }
    
    func openFile() {
        print("Open File: \(self.fileName)")
    }
    
    func modifyFile() {
        print("Modify File: \(self.fileName)")
    }
    
    func writeFile() {
        print("Write File: \(self.fileName)")
    }
    
    func closeFile() {
        print("Close File: \(self.fileName)")
    }
    
    deinit {
        print("Deinit instance")
        self.writeFile()
        self.closeFile()
    }
}

var fileManager: FileManager? = FileManager(fileName: "abc.txt")

if let manager = fileManager {
    manager.openFile()
    manager.modifyFile()
}

fileManager = nil

클래스는 디스크의 파일을 불러와 사용하는 FileManager 클래스이다. FileManager의 인스턴스가 파일을 불러와 사용하며, 인스턴스의 사용이 끝난 후에는 파일의 변경사항을 저장하고 다시 닫아줘야 메모리에서 파일이 해제되기 때문에 인스턴스가 메모리에서 해제되기 직전에 파일도 닫아주는 작업을 합니다.

 

디이셜라이저를 잘 활용하면 메모리 관리 측면 외에도 프로그래머가 설계한 로직에 따라 인스턴스가 메모리에서 해제되기 직전에 적절한 작업을 하도록 할 수 있을것이다.

 

 

포인트정리

- 초기화는 클래스나 구조체 또는 열거형의 인스턴스를 사용하기 위한 준비 과정이다.

- 초기화 과정은 새로운 인스턴스를 사용할 준비를 하기 위하여 저장 프로퍼티의 초깃값을 설정하는 일이다.

- 이니셜라이저의 역할은 그저 인스턴스의 첫 사용을 위해 초기화 하는 것 뿐이다.

- 구조체와 클래스의 인스턴스는 처음 생성할 때 옵셔널 저장 프로퍼티를 제외한 모든 저장 프로퍼티에 적절한 초기값을 할당해야한다.

- 옵셔널이 아닌이상 프로퍼티에 기본값을 할당하거나 초기값을 할당할 수 있도록 이니셜라이저를 생성해 주거나 둘 중 무조건 택1

- 이니셜라이저를 통해 초깃값을 할당하거나, 프로퍼티 기본값을 통해 처음의 저장 프로퍼티가 초기화 될때는 프로퍼티 감시자 메서드가 호출되지 않는다.

 

- 사용자 정의 이니셜라이저를 만들면 기존의 기본 이니셜라이저는 별도로 구현하지 않는 이상 사용할 수 없다.

- 인스턴스가 사용되는 동안에 값을 꼭 갖지않아도 되는 저장 프로퍼티가 있다면 해당 프로퍼티를 옵셔널로 선언할 수 있으며 저장프로퍼티는 초기화 과정에서 값을 할당해주지 않는다면 자동으로 nil이 할당된다.

 

- 상수로 선언된 저장 프로퍼티는 인스턴스를 초기화하는 과정에서만 값을 할당할 수 있으며, 처음 할당된 이후로는 값을 변경할 수 없다.

- 해당 클래스를 상속받은 자식 클래스의 이니셜라이저에서는 부모클래스의 상수 프로퍼티 값을 초기화할 수 없다.

- 기본 이니셜라이저는 프로퍼티 기본값으로 프로퍼티를 초기화해서 인스턴스를 생성한다.

- 기본 이니셜라이저는 저장 프로퍼티의 기본값이 모두 지정되어 있고, 동시에 사용자 정의 이니셜라이저가 정의되어 있지 않은 상태에서 제공된다.

 

- 구조체는 사용자 정의 이니셜라이저를 구현하지 않으면 프로퍼티의 이름으로 매개변수를 갖는 이니셜라이저인 멤버와이즈 이니셜라이저를 기본으로 제공한다. 그렇지만 클래스는 멤버와이즈 이니셜라이저를 지원하지 않는다.

 

- 멤버와이즈 이니셜라이저란 멤버와이즈 이니셜라이저는 구조체만의 특권이며 프로퍼티의 이니셜라이저를 자동으로 생성해주는 것을 말함.

- 값 타입인 구조체와 열거형은 코드의 중복을 피하기 위하여 이니셜라이저가 다른 이니셜라이저에게 일부 초기화를 위임하는 초기화 위임을 간단하게 구현할 수 있다. 하지만 클래스는 상속을 지원하는 터라 간단한 초기화 위임도 할 수 없다. (self.init을 사용한다.)

- 사용자 정의 이니셜라이저를 정의할 때도 기본 이니셜라이저나 멤버와이즈 이니셜라이저를 사용하고 싶다면 익스텐션을 사용한다.

- 이니셜라이저의 전달인자로 잘못된 값이나 적절치 못한 값이 전달되었을 때, 이니셜라이저는 인스턴스 초기화에 실패할 수 있다.

- 초기화를 실패했을 때는 return nil을 반대로 초기화에 성공했을 때는 return을 적어 초기화의 성공과 실패를 표현할 뿐, 실제 값을 반환하지 않는다.

 

- rawValue를 통한 이니셜라이저는 기본적으로 실패 가능한 이니셜라이저로 제공한다. (case에 맞지않은 값이 들어오거나 잘못된 rawValue전달이 들어올 수 있기때문)

 

- 클래스의 인스턴스는 디이니셜라이저(Deinitializer)를 구현할 수 있으며 이니셜라이저와 반대 역할을 한다. 즉, 메모리에서 해제되기 직전 클래스 인스턴스와 관련하여 원하는 정리 직업을 구현할 수 있다.

 

- 클래스에는 디이니셜라이저를 단 하나만 구현할 수 있다. 디이니셜라이저는 이니셜라이저와는 다르게 매개변수를 갖지 않으며, 소괄호도 적어주지 않는다.

 

궁금증

- deinit은 init()메서드를 생성해서 인스턴스를 접근해야지만 호출이 될 수 있는것일까 ?

- var quokka = Animal() 과 var quokka: Animal = Animal()은 같은 표현이라고 이해하고 있는데

  전자 같은경우에는 별다른거 없이 Animal타입으로 초기화해서 인스턴스화 시키고 싶을때 간단하게 사용할 수 있고

  후자 같은경우에는 옵셔널타입으로 지정하고 싶을 경우에 사용되는 방법? 이라고 받아들였는데 이렇게 생각해도 괜찮은걸까?

 

 

 

Reference

- 야곰책 3판 Swift 프로그래밍