[Swift] 인스턴스 생성 및 소멸
👨🏻‍💻iOS 공부/Swift 기본기 다지기

[Swift] 인스턴스 생성 및 소멸

728x90
반응형

언뜻 보면 제비...

지금까지 structclass를 생성할 때에 기본 이니셜라이저를 활용하여 인스턴스를 생성했다! 초기화가 완료된 인스턴스는 사용 후 소멸 시점이 오면 소멸되게 된다. 인스턴스를 생성하고 소멸시키는 방법에 대해서 알아보자.

인스턴스 생성

초기화는 새로운 인스턴스 사용 준비를 위해 저장 프로퍼티의 초깃값을 설정하는 역할을 한다. 즉, 기본값을 가질 수 있도록 세팅해주는 것이다. 이는 이니셜라이저를 구성하여 구현할 수 있다. 스위프트의 이니셜라이져는 값을 반환하는 형태가 아니라 초기화하는 역할 그 자체이다.

아래의 코드처럼 init 키워드를 사용하여 구현할 수 있다.

class someClass {
    init() {
        // 초기화에 필요한 코드
    }
}

struct someStruct {
    init() {
        // 초기화에 필요한 코드
    }
}

enum someEnum {
    var someCase
    init() {
        // enum은 초기화 시, 반드시 case 중 하나이어야 한다. 
        self = .someCase
        // 초기화에 필요한 코드 
    }
}

 

프로퍼티 기본값

구조체와 클래스의 인스턴스는 처음 생성시, 옵셔널 저장 프로퍼티를 제외한 모든 저장 프로퍼티에 적절한 초깃값을 할당해야 한다.(옵셔널 저장 프로퍼티의 경우 값이 없을 수도 있기에 예외이다.) 프로퍼티를 정의할 때, 프로퍼티 기본값을 할당하면 이니셜라이저에서 따로 초깃값을 할당하지 않아도 프로퍼티 기본값으로 저장 프로퍼티의 값이 초기화된다.

// 이니셜라이져로 초깃값 할당
struct Library {
    var category: String

    init() {
        category = "IT" // 초깃값 할당
    }
}

let bookCat: Library = Library()
bookCat.category // "IT"

String 타입의 저장 프로퍼티를 가지고 있는 것을 볼 수 있다. 여기서 초기화시에 초깃값을 할당하여 주는 모습을 볼 수 있다. 또한 이외에도 이니셜라이져로 초깃값을 할당하는 것이 아니라 저장 프로퍼티에 초깃값을 할당할 수 있다.

// 저장 프로퍼티 활용하여 초깃값 할당
struct Library {
    var category: String = "IT"
}

let bookCat: Library = Library()
bookCat.category // "IT"

이외에도 이니셜라이저의 매개변수, 옵셔널 프로퍼티, 상수 프로퍼티에 값을 할당하여 초기화 과정을 구현해 볼 수 있다. 차례대로 한 가지씩 살펴보자.

 

이니셜라이저 매개변수

함수, 메서드처럼 이니셜라이저 또한 매개변수를 가질 수 있다. 인스턴스를 초기화하는 과정에 필요한 값을 매개변수로 받을 수 있다는 것이다.

struct ExchangeValue{ 
    var won: Int

    init(realUS US: Int) {
        won = US / 1160
    }

    init(realWon won: Int) {
        self.won = won // self 사용하여 전달인자 레이블과 다르게 표시
    }

    init(value: Int) {
        won = value
    }

    init(_ value: Int) {
        won = value
    }
}

let myWon: ExchangeValue = ExchangeValue(realUS: 10)
myWon.won // 11,600

let yourWon: ExchangeValue = ExchangeValue(realWon: 1000)
yourWon.won // 1,000

let groupWon: ExchangeValue = ExchangeValue(value: 1000)
let indiWon: ExchangeValue = ExchangeValue(1000)

ExchangeValue() // 에러! 

첫 번째 realUS는 달러 환율을 반영하여 원화로 환산한 값을 won프로퍼티에 할당하는 이니셜라이저이고, realWon은 값을 입력받아 그대로 won프로퍼티에 할당하는 이니셜라이저이다.
세 번째 이니셜라이저에서는 따로 전달인자를 사용하지 않았다. 매개변수 value가 있지만, 네 번째처럼 필요없을 경우 _를 사용하여 전달인자 레이블을 없앨 수도 있다.

 

옵셔널 프로퍼티 타입

꼭 초기화하지 않아도(값을 갖지 않아도) 되는 저장 프로퍼티가 있을 경우에 속한다. 혹은 초깃값을 할당하기 어려운 경우, 옵셔널 프로퍼티 타입으로 선언할 수 있다. 이 때, 값을 할당해주지 않았기 때문에 자동으로 nil이 할당된다!

class Speaker {
    var name: String
    var volume: Int?

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

let google: Speaker = Speaker(name: "googleSpeaker")
google.name // "googleSpeaker"
google.volume // nil

google.volume = 80
google.volume // 80 

google.name = "Clova"
google.name // "Clova"

볼륨을 모를 경우 값을 할당해주지 않을 수도 있다. 이 때는 옵셔널 프로퍼티이기에 nil을 반환한 모습을 볼 수 있다. 이후에 다시 값을 할당할 수 있으니 걱정하지 않아도 된다!

 

상수 프로퍼티

위의 경우처럼 구글 스피커인데, 이름은 clova로 바꾸니 너무 쉽게 변경되어버렸다... 이러한 경우를 방지하기 위해 프로퍼티를 let으로 선언해줘야 한다. 상수로 선언했기 때문에, 초기화 과정에서만 값을 할당할 수 있다는 점 참고하여 값을 할당하면 된다. 즉, 처음에 할당한 값 이외에는 변경이 안된다는 것이다.

class Speaker {
    let name: String
    var volume: Int?

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

let google: Speaker = Speaker(name: "google")
google.name // "google"
google.name = "Clova" // cannot asign property "name" is "let"
// google로 할당하여서, 새 이름을 붙일 수 없다.

 

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

지금까지 사용자 정의 이니셜라이저에 대해서만 알아보고 "기본" 이니셜라이저에 대해서는 알아보지 않았다. 기본 이니셜라이저는 프로퍼티 기본값으로 프로퍼티를 초기화하여 인스턴스를 생성한다. 즉, 기본 이니셜라이저는 저장 프로퍼티의 기본값이 모두 지정되어있고, 사용자 정의 이니셜라이저가 정의되어 있지 않은 상태에서 제공된다.

저장 프로퍼티를 선언할 때, 기본값을 지정해주지 않으면 이니셜라이저에서 설정해야한다. 다만 매번 이니셜라이저를 선언하기에 귀찮은 경우가 많다. struct에서는 이 때에는 프로퍼티의 이름으로 매개변수를 가지는 멤버와이즈 이니셜라이저를 기본으로 제공한다. Speaker(name: , volume: )처럼 자동적으로 프로퍼티의 이름을 사용하여 기본값을 할당할 수 있다. 하지만 class의 경우에는 멤버와이즈 이니셜라이저를 지원하지 않는다.

struct Theater {
    var place: String = "A"
    var size: Int = 200    
}

let cgv: Theater = Theater(place: "gang-nam", size : "300")

// 위 경우 처럼 기본값이 이미 있는 경우에는 필요한 매개변수만 사용하여 초기화 할 수 있디.
let indiTheater: Theater = Theater()
let megabox: Theater = Theater(place: "sin-chon")
let lotte: Theater = Theater(size: 100)

멤버와이즈 이니셜라이즈는 struct가 가지는 특권이다!

 

초기화 위임

값 타입인 구조체와 열거형은 코드 중복을 피하기 위해 이니셜라이저가 다른 이니셜라이저에게 일부 초기화를 위임할 수 있다. 하지만 클래스는 상속을 지원하기에 초기화 위임을 할 수 없다.

값 타입에서 이니셜라이저가 다른 이니셜라이저를 호출하기 위해서는 self.init을 사용한다. 이는 당연히 이니셜라이저 안에서만 사용될 수 잇다.(사용자 정의 이니셜라이저를 가리키는 것) 다만 사용자 이니셜라이저를 정의하면 기본 이니셜라이저와 멤버와이즈 이니셜라이즈를 사용할 수 없기에, 초기화 위임을 위해서는 최소 2개 이상의 사용자 정의 이니셜라이저를 구현해야 한다.

enum Tier {
    case bronze, silver, gold
    case iron

    // 있어야 기본 이니셜라이저 사용 가능 
    // var UserA: Tier = Tier()
    // print(UserA) -> iron
    init() {
        self = .iron
    }

    init(mmr: Int) {
        switch mmr {
        case 100...1000:
            self = .bronze
        case 1000...1300:
            self = .silver
        case 1300...1600:
            self = .gold
        default:
            self = .iron
        } 
    }

    init(beforeMMR: Int, increse: Int) {
        self.init(mmr = beforeMMR + increase)
    }
}

var UserA: Tier = Tier(mmr: 1005)
pirnt(UserA) // silver

UserA = Tier(beforeMMR: 1005, increase: 500)
print(UserA) // gold

이렇게 초기화 위임을 통해 중복되는 코드를 작성하지 않고 효율적으로 이니셜라이저를 만들 수 있다.

 

실패 가능한 이니셜라이저

이니셜라이저의 전달인자로 잘못된 값이나 적절하지 못한 값이 할당되었을 때 인스턴스 초기화에 실패할 수 있다. 이외에도 여러가지 이유가 있지만, 실패 가능성을 염두에 두어야 한다. 이를 실패 가능한 이니셜라이저라고 부르며, 실패 시에는 nil을 반환해주므로 반환 타입이 옵셔널로 지정된다. 이에 실패 가능한 이니셜라이저는 init이 아닌 init? 키워드를 사용해준다.

class Developer {
    var language: String
    var career: Int?

    init?(name: String){
        if name.isEmpty {
            return nil
        }

        self.name = name
    }

    init?(name: String, career: Int){
        if name.isEmpty || career < 1 {
            return nil
        }

        self.name = name
        self.career = career
    }
}

let chamin: Developer? = Developer(name: "Cha", career: "2")

if let swift: Developer = chamin {
    print(swift.name)
} else {
    print("swift 초기화 실패!")
} // "Cha"

let other: Developer? = Developer(name: "B", career: 0)

if let swift: Developer = other {
    print(swift.name)
} else {
    print("swift 초기화 실패!")
} // "swift 초기화 실패!"

// 반대로 name: ""인 경우도 else 구문에 해당 

// enum의 경우에 rawValue를 활용하여 실패 가능한 이니셜라이저를 구현해볼 수 있다. 
enum Tier: String {
    case bronze = "bronze", silver = "silver", gold = "gold"

    init?(mmr: Int) {
        switch mmr {
        case 100...1000:
            self = .bronze
        case 1000...1300:
            self = .silver
        case 1300...1600:
            self = .gold
        default:
            return nil
        }
    }
}

var ayo: Tier? = Tier(mmr:100) //nil

ayo = Tier(rawValue: "platinum")
print(ayo) // nil

ayo = Tier(rawValue: "silver")
print(ayo!) // silver

 

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

인스턴스 초기화시 함수나 클로저를 호출하면서 연산 결과값을 프로퍼티 기본값으로 제공해줄 수 있다. 그렇기에 함수/클로져의 반환 타입은 프로퍼티 타입과 일치해야한다.

만약 아래와 같이 클로저 사용시, 클로저가 실행되는 시점은 초기화 할 때 인스턴스의 다른 프로퍼티 값이 설정되기 전 이라는 점을 인지해야한다. 이에 클로저 내부에서는 인스턴스의 다른 프로퍼티를 사용하여 연산할 수 없다.

class someClass {
    let someProperty: someType = {
        // 사용자 정의 연산 후 반환
        // 반환 값 타입은 동일하게 someType
        return someValue
    }() 
    // 클로저 실행을 위해 ()를 붙인다. -> 실행 결과값이 프로퍼티 기본값이 된다. 
    //소괄호가 없다면 클로저 자체가 프로퍼티의 기본값이 된다!
}

 

인스턴스 소멸

deinit 키워드를 사용하여 디이니셜라이저를 구현할 수 있다. 이니셜라이저와 반대 역할로 원하는 정리 작업을 할 수 있다. 디이니셜라이저는 "클래스"의 인스턴스에만 구현할 수 있다는 점 참고하자!

class FileManager {
    var filename: String

    init(filename: Stirng){
        self.filename = filename
    }

    func open(){
        print("open file")
    }

    func close(){
        print("close file")
    }

    func write(){
        print("write file")
    }

    func modify(){
        print("modify file")
    }

    deinit {
        // ()는 안붙여도 된다. 
        print("deinit")
        self.write()
        self.close()
    }
}

var fileManager: FileManager? = FileManager(filename = "aaa.txt")

if let manager: FileManager = fileManager {
    manager.open() // open file
    manager.modify() // modify file
}

fileManager = nil 
// "deinit"
// "write file"
// "close file"

deinit하기 전에 파일을 작성하고, 종료하는 등 원하는 행위를 지정하여 실행할 수 있다~!
잘 활용하면 메모리 관리 측면에서도 좋다고 하니 유용하게 잘 활용하면 좋을 듯 하다!

 

 

끄으읕.

728x90
반응형