콘텐츠로 건너뛰기

[야곰 Swift 프로그래밍] 11 인스턴스 생성 및 소멸 (init & deinit)

11. 인스턴스 생성 및 소멸

  • 초기화는 클래스나 구조체 또는 열거형의 인스턴스를 사용하기 위한 준비 과정
  • 초기화가 완료된 인스턴스는 사용 후 소멸 시점이 오면 소멸함

11.1 인스턴스 생성

  • 초기화: 새로운 인스턴스를 사용할 준비를 하기 위하여 저장 프로퍼티의 초기값을 설정하는 등의 일
  • 이니셜라이저(Initializer)를 정의하면 초기화 과정을 직접 구현할 수 있음
  • init 키워드를 사용하여 이니셜라이저 메서드임을 표현

11.1.1 프로퍼티 기본 값

  • 옵셔널 저장 프로퍼티를 제외한 모든 저장 프로퍼티에 적절한 초기값을 할당해야 함
  • 초기화 후 값이 확정되지 않은 저장 프로퍼티는 존재할 수 없음

11.1.2 이니셜라이저 매개변수

// 11.1.2 이니셜라이저 매개변수

struct Area{
    var squareMeter :Double
    
    // 사용자 정의 이니셜라이저를 만들면 기존의 기본 이니셜라이저(init())은 별도로 구현하지 않는 이상 사용할 수 없음
    init(fromPy py: Double){
        squareMeter = py * 3.3058
    }
    
    init(fromSquareMeter squareMeter: Double){
        self.squareMeter = squareMeter
    }
    
    // 전달인자 레이블을 사용하지 않았음. 별다른 의미 없는 value라는 이름의 매개변수로 있으므로, 전달인자 레이블 value가 필요하지 않다면
    // 네 번째 이니셜라이저처럼 와일드카드 식별자(_)를 사용하여 전달인자 레이블을 없애줄 수 있음
    init(value: Double){
        squareMeter = value
    }
    
    // 와일드카드 식별자(_)를 사용하여 전달인자 레이블을 없앰
    init(_ value: Double){
        squareMeter = value
    }
}

let roomOne: Area = Area(fromPy: 15.0)
print(roomOne.squareMeter)

let roomTwo: Area = Area(fromSquareMeter: 33.06)
print(roomTwo.squareMeter)


let roomFour: Area = Area(value: 55.0)
print(roomFour.squareMeter)

// 네번째 이니셜라이저를 사용. 전달인자 레이블을 없앰
let roomThree: Area = Area(30.0)
print(roomThree.squareMeter)

11.1.3 옵셔널 프로퍼티 타입

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

class Person{
    var name: String
    var age: Int?

    
    init(name: String){
        self.name = name
        // self.age 프로퍼티에 값을 할당해주지 않기때문에, 자동으로 nil이 할당 됨
    }
}

let yagom: Person = Person(name: "yagom")
print(yagom.name)
print(yagom.age) // nil

yagom.age = 33
print(yagom.age)

11.1.3 옵셔널 프로퍼티 타입

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

class Person{
    var name: String
    var age: Int?

    
    init(name: String){
        self.name = name
        // self.age 프로퍼티에 값을 할당해주지 않기때문에, 자동으로 nil이 할당 됨
    }
}

let yagom: Person = Person(name: "yagom")
print(yagom.name)
print(yagom.age) // nil

yagom.age = 33
print(yagom.age)

11.1.4 상수 프로퍼티

// 상수로 선언된 저장 프로퍼티는 인스턴스를 초기화하는 과정에서만 값을 할당할 수 있음. 처음 할당된 이후로는 값을 변경할 수 없음
// 클래스 인스턴스의 상수 프로퍼티는 프로퍼티가 정의된 클래스에서만 초기화 할 수 있음. 해당 클래스를 상속받은 자식클래스의 이니셜라이저에서는 부모클래스의 상수 프로퍼티 값을 초기화할 수 없음

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

var yagom: Person = Person(name: "yagom")
// yagom.name = "Eric" // 오류 발생! Cannot assign to property. 'name' is a 'let' constant

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

//11.1.5 기본 이니셜라이저와 멤버와이즈 이니셜라이저
// 사용자 정의 이니셜라이저를 정의해주지 않으면 클래스와 구조체는 모든 프로퍼티에 기본값이 지정되어 있다는 전제하에 기본 이니셜라이저를 사용함
// 기본 이니셜라이저는 프로퍼티 기본값으로 프로퍼티를 초기화하여 인스턴스를 생성함
// 즉, 기본 이니셜라이저는 저장 프로퍼티의 기본값이 모두 지정되어 있고, 동시에 사용자 정의 이니셜라이저가 정의되지 않은 상태에서 제공됨

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

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 = Point(x: 5, y: 10)
let size: Size = Size(width: 50, height: 20)

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

11.1.6 초기화 위임 (Initializer Delegation)

// 11.1.6 초기화 위임 (initializer Delegation)
// 값 타입인 구조체와 열거형은 코드의 중복을 피하기 위해 이니셜라이저가 다른 이니셜라이저에게 일부 초기화를 위임하는 초기화 위임을 구현할 수 있음
// 참조 타입인 클래스는 상속을 지원하기 때문에 초기화 위임을 할 수 없음

enum Student{
    case elementry, middle, high
    case none
    
    // 사용자 정의 이니셜라이저가 있는 경우, init() 메서드를 구현해주어야 기본 이니셜라이저를 사용할 수 있음
    init(){
        self = .none
    }
    
    init(koreanAge: Int){
        switch koreanAge{
        case 8...13:
            self = .elementry
        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 lucy: Student = Student(koreanAge: 18)
print(lucy)

lucy = Student(bornAt: 2008, currentYear: 2022)
print(lucy)

11.1.7 실패 가능한 이니셜라이저

// 11.1.7 실패 가능한 이니셜라이저(failable initializer)

// 이니셜라이저를 통해 인스턴스를 초기화할 수 없는 여러가지 예외 상황이 있음. 대표적으로 이니셜라이저의 전달인자로 잘못된 값이 전달되었을 때.
// 실패가능성을 내포한 이니셜라이저를 실패 가능한 이니셜라이저라고 부름
// 클래스, 구조체, 열거형 등에 모두 적용 가능
// 실패 가능한 이니셜라이저는 실패했을 때 nil을 반환해주므로 반한 타입을 옵셔널로 지정
// init 대신 init? 키워드 사용

class Person{
    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
    }
}


// 에러! nil을 리턴할 수 있기 때문에, Person? Person! 으로 타입을 지정하거나 ??로 Nil-coalescing을 해주어야 한다
// let yagom: Person = Person(name: "yagom", age: 30)

let yagom: Person? = Person(name: "yagom", age: 30)

// optional binding. non-optional 변수인 person에 optional 변수인 yagom을 할당할 수 있다면 {}안의 내용을 실행하라
if let person: Person = yagom {
    print(person.name)
}else{
    print("Person wasn't initialized")
}
// print yagom

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

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



11.1.8 함수를 사용한 프로퍼티 기본값 설정

// 11.1.8 함수를 사용한 프로퍼티 기본값 설정
// 사용자 정의 연산을 통해 저장 프로퍼티 기본값을 설정하고자 한다면 클로저나 함수를 사용하여 프로퍼티 기본값을 제공할 수 있음
// 클로저를 사용한다면, 클로저가 실행되는 시점은 초기화할 때 인스턴스의 다른 프로퍼티 값이 설정되는 것이라는 것을 명심해야 함
// 즉, 클로저 내부에서는 인스턴스의 다른 프로퍼티를 사용할 수 없음. 다른 프로퍼티에 기본값이 있다고 해도 안 됨

import Foundation

struct Student {
    var name: String?
    var age: Int?
}

class SchoolClass {
    var students: [Student] = {
        // 새로운 인스턴스를 생성하고 사용자 정의 연산을 통한 후 반환해 줌
        // 반환되는 값의 타임은 [Student] 타입이어야 함
        var arr: [Student] = [Student]()
        
        for num in 1...15 {
            var student: Student = Student(name: nil, age: nil)
            arr.append(student)
        }
        return arr
    }()
    // 클로저 뒤에 소괄호가 붙는 이유는 클로저를 실행하기 위함
    // 클로저를 실행한 결과값이 프로퍼티의 기본값이 됨
    // 소괄호가 없다면 프로퍼티의 기본값은 클로저 그 자체가 됨
}

let myClass: SchoolClass = SchoolClass()
print(myClass.students.count)

11.2 인스턴스 소멸

// 11.2 인스턴스 소멸
// Deinitlizer
// Deinitlizer는 initalizer와 반대 역할을 함. 메모리에서 해제되기 직전 클래스의 인스턴스와 관련하여 원하는 정리 작업을 구현할 수 있음
// 클래스의 인스턴스가 메모리에서 소멸되기 직전에 호출됨
// deinit 키워드 사용
// 클래스의 인스턴스에만 구현할 수 있음
// 스위프트는 인스턴스가 더 이상 필요하지 않으면 자동으로 메모리에서 소멸시킴.
// 인스턴스 대부분은 소멸시킬 때 디이니셜라이저를 사용해 별도의 메모리 관리 작업을 할 필요가 없음
// 하지만 예를 들어 인스턴스 내부에서 파일을 불러와 여는 등 외부 자원을 사용했다면 인스턴스를 소멸하기 전에 파일을 다시 저장하고 닫는 등의 작업이 필요.
// 이 경우 디이니셜라이저 사용 가능
// 디이니셜라이저는 단 하나만 구현할 수 있으며, 자동으로 호출되기 때문에 별도의 코드로 호출할 수 없음

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 closeFile() {
        print("close file: \(self.fileName)")
    }
    
    deinit {
        print("Deinit instance")
        self.closeFile()
    }
}

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

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

fileManager = nil
// Deinit instance