본문 바로가기
etc/Swift

인스턴스 생성과 소멸

by IT learning 2021. 5. 28.
728x90

 

오늘은 인스턴스 생성과 소멸에 대해 알아보았다.

 

생성은 init, 소멸은 deinit 키워드로 만든다.

 

보통의 프로퍼티 초기화

class PersonA {
    // 모든 저장 프로퍼티에 기본값 할당
    var name: String = "unknown"
    var age: Int = 0
    var nickName: String = "nick"
}

let jason: PersonA = PersonA()
jason.name = "jason"
jason.age = 30
jason.nickName = "j"

// 우리는 초기화와 동시에 프로퍼티에 값을 할당하고 싶은데, 그럴 방법이 없다 이말이다.

보통은 이렇게 먼저 클래스에서 프로퍼티를 생성해주고 난 뒤, 기본값을 할당 해주어야 한다. 안그러면 컴파일러가 뺴애애액 거린다.

그리고 나와서 프로퍼티에 값을 할당하려면, 저렇게 하나씩 하나씩 할당을 한다. 근데,,다른언어들보면 생성을 하면서 초기화를 하는게 이상적이지 않나? 라는 생각을 할 수가 있다. 그럼 스위프트는 그런걸 못하는 언어인가..?

 

응 아냐 - 이니셜라이저(init)

class PersonB {
    var name: String
    var age: Int
    var nickName: String
    
    // 이니셜라이저
    init(name: String, age: Int, nickName: String) {
        self.name = name
        self.age = age
        self.nickName = nickName
    }
}

let hana: PersonB = PersonB(name: "hana", age: 20, nickName: "하나")

// 하나가 없다면?
//let hana: PersonB = PersonB(name: "haha", age: 20, nickName: "")

다행이 있었다 ㅎㅎ ㅋㅋ. 클래스에서 먼저 기본값이 없는 프로퍼티를 생성해주고 난 뒤 , init 이라는 키워드로 생성자를 만들 수 있다.

그리고 사용법은 인스턴스를 생성하며 초기화를 바로 해주는 방법이다. 그런데 문제가 하나 있다.

 

프로퍼티 중에 닉네임 프로퍼티가 있는데, 마지막 줄 '하나'는 닉네임이 없다. 그래서 빈칸으로 남겼다. 근데 컴파일 에러가 난다.

아니 닉네임 없는 사람 차별하는건가? 이걸 해결하는 방법도 존재한다.

 

프로퍼티의 초기값이 꼭 필요는 없을 때

class PersonC {
    var name: String
    var age: Int
    var nickName: String? // 옵셔널 사용
    
    convenience init(name: String, age: Int, nickName: String) {
        self.init(name: name, age: age) // 중복된 것들은 이미 있는 init을 불러와 사용이 가능하다
        self.nickName = nickName
    }
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}


let jenny: PersonC = PersonC(name: "jenny", age: 10)
let mike: PersonC = PersonC(name: "mike", age: 15, nickName: "M")

이럴 때는 옵셔널을 사용하면 된다. 옵셔널을 이용해 있을 수도 있고 없을 수도 있습니다. 라는 의미를 띄워주면 된다. 그렇게 하면 닉네임이 없어도 잘 생성된다.

중간에 init 옆에 convenience 라는 키워드가 붙었는데, 아래에 또 init이 같은 값을 받아오고 있는데, 굳이 두 init에 똑같은 코드를 쓸 필요가 없어서 아래 init을 불러오기 위해서는 convenience 키워드를 붙여야 사용이 가능하다.

 

암시적 추출 옵셔널의 사용

class Puppy {
    var name: String
    var owner: PersonC! // 꼭 필요한 경우에 느낌표를 찍어준다.
    
    // 이렇게 먼저 이름부터 설정하고 나중에 PersonC를 사용할때 느낌표를 달아준다.
    init(name: String) {
        self.name = name
    }
    
    func goOut() {
        print("\(name)가 주인 \(owner.name)와 산책을 합니다")
    }
}

let happy: Puppy = Puppy(name: "Happy")
// happy.goOut()
happy.owner = jenny
happy.goOut()
// happy가 주인 jenny와 산책을 합니다.

// 이렇게 나중에 주인을 지정할 때 암시적 추출 옵셔널을 사용한다

이번엔 반대의 의미이다. 이번에는 프로퍼티가 꼭 필요한 경우에 사용하는 것으로 암시적 추출 옵셔널인 느낌표(!)를 사용하면 된다.

느낌표를 사용하게 될 경우 init 에 자신이 일단 설정할 것만 추가하고 코드를 마쳐도 괜찮다.

위 코드는 강아지 클래스인데, 이 클래스 안에 저장 프로퍼티는 강아지 이름, 주인 이 두가지가 존재한다.

강아지는 혼자서는 산책을 나갈 수 없기 때문에, happy라는 인스턴스를 생성하고 바로 나가면 에러가 발생한다. 왜냐? 주인이 아직 정해지지 않았기 때문이다. 이게 바로 아까 느낌표를 설정한 것이다. 이때 아까 설정하지 않은 owner, 즉 주인을 설정해주고 난 뒤에 밖으로 나간다는 메소드를 실행시키면 출력이 되는 것 이다. 이렇게 나중에 지정을 할 때 사용을 할 수 있다.

 

실패가능한 이니셜라이저

class PersonD {
    var name: String
    var age: Int
    var nickName: String?
    
    init?(name: String, age: Int) {
        if (0...120).contains(age) == false {
            return nil
        }
        
        if name.count == 0 {
            return nil
        }
        
        self.name = name
        self.age = age
    }
}

// 옵셔널이 아닌 타입으로 인스턴스를 만들려고 하면 안된다.
//let john: PersonD = PersonD(name: "john", age: 23)
let john: PersonD? = PersonD(name: "john", age: 23)
let joker: PersonD? = PersonD(name: "joker", age: 123)
let batman: PersonD? = PersonD(name: "", age: 10)

print(joker) // nil
print(batman) // nil

만약 이니셜라이저 매개변수로 전달되는 초깃값이 잘못된 경우 인스턴스 생성에 실패를 할수가 있다.

만약에 실패를 하면 nil을 반환할 수 있게 코드에서 처리를 해주는 것이다. 생성 실패했다고 프로그램이 종료되면 안되니까.

그래서 실패가능한 이니셜라이저 반환타입은 옵셔널 타입이다. 실패할수도 있고 아닐수도 있으니 말이다.

 

위 코드의  init을 보게 되면, 조건문으로 0살부터 120살 이내가 아닐경우에 nil을 반환한다고 나와있다.

그리고 이름이 아얘 입력되지 않았을 경우에도 nil을 반환한다. 오류 케이스를 만들어주어서 프로그램이 비정상 종료가 되지 않게 설정 할 수 있다. 실제 앱을 만들때 가장 중요할 것 같다.

 

디이니셜라이저

class PersonE {
    var name: String
    var pet: Puppy?
    var child: PersonC
    
    init(name: String, child: PersonC) {
        self.name = name
        self.child = child
    }
    
    deinit {
        if let petName = pet?.name {
            print("\(name)가 \(child.name)에게 \(petName)를 인도합니다")
            self.pet?.owner = child
        }
    }
}


var donald: PersonE? = PersonE(name: "donald", child: jenny)
donald?.pet = happy
donald = nil // donald 인스턴스가 더이상 필요없으므로 메모리에서 해제됩니다
// donald 가 jenny에게 happy를 인도합니다

디이니셜라이저는 말 그대로 이니셜라이저가 생성이었다면 소멸의 단계이다. 소멸하는 시점에서 , 즉 메모리에서 해제되는 시점에서 호출된다. 인스턴스가 해제되는 시점에 해야할 일을 구현할 수 있다라는 말이다.

 

위 코드는 강아지의 주인이 바뀌는 경우의 코드를 작성한것이다. 원래 주인이었던 donald가 가족인 jenny에게 강아지를 인도하는 과정인데, donald가 해제되면서 deinit이 발동하고, 원하는 출력문이 작성된다.

728x90

'etc > Swift' 카테고리의 다른 글

타입 캐스팅(Type Casting)  (0) 2021.05.28
옵셔널 체이닝(부제 - 갈고리 살인마)  (0) 2021.05.28
상속  (0) 2021.05.28
프로퍼티 감시자  (0) 2021.05.27
프로퍼티  (0) 2021.05.27

댓글

IT_learning's Commit