instance init / deinit (생성자, 소멸자)
why?
프로젝트를 진행하면서 모델을 구성하는 도중 다음과 같은 문제를 겪게 됬습니다.
(1) 일반적으로 model을 구성할 때 class와 struct를 사용할 수 있지만,
대부분 struct를 더 많이 사용한다고 한다.
왜 struct를 class보다 더 많이 쓰고 언제 구분하는걸까?
(2) 코드에 적혀있는 init과 비슷한 이름인 deinit은 무슨 역할을 하며,
어떻게 사용하는걸까?
(3) Codable, init(snapshot: QueryDocumentSnapshot)는 정확히 어떻게 사용될까?
이 중
init과 deinit의 정확한 내용과 사용방법에 대해서 알아야
class와 struct의 비교 및 사용에 대해 이해할 수 있을 것 같기 때문에
이 부분에 대해 먼저 공부해보고자 합니다.
init과 deinit
init은 initializer(생성자)의 약자이며,
deinit은 deinitializer(소멸자)의 약자입니다.
Swift의 모든 인스턴스는
초기화와 동시에 모든 프로퍼티에 유효한 값이 할당되어 있어야합니다.
때문에 미리 프로퍼티에 기본값을 할당해두면,
인스턴스가 생성됨과 동시에 초기값을 가지게 됩니다.
기본값(초기값)을 저장하기가 까다로운 경우,
이니셜라이저를 사용하여 인스턴스가 가지는 초기값을 전달할 수 있습니다.
사용하는 이유
init을 사용함으로써
인스턴스의 생성과 동시에 프로퍼티의 초기값을 할당할 수 있고
이로 인해 불필요한 코드를 줄일 수 있다는 장점을 가지고 있습니다.
만약 초기값을 저장하기 어렵거나, 반드시 필요는 없는 경우 옵셔널을 사용하면 됩니다.
코드를 보면서 이해할 수 있도록 해봅시다. :)
1. 초기값을 이미 설정한 경우
// 1. 초기값을 이미 설정한 경우
class SubwayStation {
// 기본값을 할당합니다.
var name: String = "Seoul"
var linenumber: Int = 1
}
// 인스턴스 생성
let Transportation: SubwayStation = SubwayStation()
// 기본값을 할당해준 상태이기 때문에 인스턴스가 생성되면서 초기값을 가져옵니다.
print("지하철역 이름: \(Transportation.name), 호선: \(Transportation.linenumber)")
//=====출력결과=====
//지하철역 이름: Seoul, 호선: 1
class를 만들 때부터 이미 기본값을 할당해주었습니다.
2. init를 통한 초기값 전달
// 2. init을 통한 초기값 전달
class SubwayStation {
// 기본값을 할당하지 않고, 변수타입만 선언합니다.
var name: String
var linenumber: Int
// init
init(name: String, linenumber: Int) {
self.name = name
self.linenumber = linenumber
}
}
// 인스턴스를 생성하면서 init을 통해 property 값을 할당해줍니다.
let Transportation: SubwayStation = SubwayStation.init(name: "Seoul", linenumber: 1)
// init에서 할당한 값을 출력합니다.
print("지하철역이름: \(Transportation.name), 호선: \(Transportation.linenumber)")
// =====출력결과=====
// 지하철역이름: Seoul, 호선: 1
이니셜라이저를 통해 변수타입만 선언한 뒤 추후 인스턴스를 생성하며 입력한 값이 출력되는 것을 볼 수 있습니다.
1번과의 차이점이 보이시나요?
3. 초기값을 저장하기 어렵거나 애매한 경우( optional 사용 )
// 3. 초기값을 저장하기 어렵거나 애매한 경우(옵셔널 사용)
class SubwayStation {
// 기본값을 할당하지 않고, 변수타입만 선언합니다.
var name: String
var linenumber: Int
// + 옵셔널을 사용하여, 동일하게 변수타입을 선언합니다. 이 변수에 값을 할당할 수도 있고, 안할 수도 있습니다.
var color: String?
// color변수를 가지고 있는 init과
init(name: String, linenumber: Int, color: String) {
self.name = name
self.linenumber = linenumber
self.color = color
}
// // color변수가 없는 init을 생성합니다.
init(name: String, linenumber: Int) {
self.name = name
self.linenumber = linenumber
}
}
// 인스턴스를 생성하면서 init을 통해 property 값을 할당해주면서
// 차이점을 비교하기 위해 2가지 경우를 만들어봅니다.
let Transportation1: SubwayStation = SubwayStation.init(name: "Seoul", linenumber: 1, color: "blue")
let Transportation2: SubwayStation = SubwayStation.init(name: "Gangnam", linenumber: 2)
// init에서 할당한 값을 출력합니다.
print("지하철역이름: \(Transportation1.name), 호선: \(Transportation1.linenumber), 호선색상: \(Transportation1.color)")
print("지하철역이름: \(Transportation2.name), 호선: \(Transportation2.linenumber), 호선색상: \(Transportation1.color)")
// =====출력결과=====
// 지하철역이름: Seoul, 호선: 1, 색상: Optional("blue")
// 지하철역이름: Gangnam, 호선: 2, 색상: nil
이번엔 color라는 새로운 변수를 추가하고 옵셔널로 지정해주었습니다.
Transportation2 상수에 color 변수 초기값을 할당해주지 않았지만,
print함수에서 호출할 때 nil이 출력되는 것을 볼 수 있습니다.
Transportation1 상수에는 초기값을 넣어주었지만,
Optioanl("blue"), 즉 옵셔널 타입의 값으로 출력되었네요.
어떤 모델을 만들다보면 사용자가 선택적으로 입력할 수 있는 칸들이 있을 수 있습니다.
(ex. 회원가입의 성별이라던지, 직업 등등)
즉, 초기값이 필요없을 때 활용한다면 좋겠네요.
4. 초기값이 필요없을 때
프로퍼티의 초기값이 꼭 필요 없을 때는
(1) optional 사용 (인스턴스 사용에 꼭 필요하지만 초기값을 할당하지 않고자 할 때 사용하면 됨)
(2) 클래스 내부의 init을 사용할 때는 convenience 키워드 사용
// 3.+ 클래스 내부의 init을 사용할 때는 convenience 키워드 사용하기
class SubwayStation {
// 기본값을 할당하지 않고, 변수타입만 선언합니다.
var name: String
var linenumber: Int
var color: String?
// 🟨 init
init(name: String, linenumber: Int, color: String) {
self.name = name
self.linenumber = linenumber
self.color = color
}
// 🟨 class 내부에 init를 사용하려면 convenience keyword를 사용해줘야합니다.
// 위 init부분과 동일한 기능을 수행합니다.
convenience init(name: String, linenumber: Int, color: String) {
self.init(name: name, linenumber: linenumber)
self.color = color
}
init부분과 convenience init은 같은 기능을 수행하지만
class 내부라는 조건이 있습니다.
5. 실패가능한 이니셜라이저.
init 변수로 전달되는 초기값이 잘못되는 경우 인스턴스 생성에 실패할 수도 있습니다.
(값의 범위를 벗어나는 경우)
만약 이럴 경우 nil을 반환하며, 반환타입은 옵셔널타입입니다.
init? 을 사용하게 됩니다.
// 5. 실패가능한 이니셜라이저
class SubwayStation {
// 기본값을 할당하지 않고, 변수타입만 선언합니다.
var name: String
var linenumber: Int
// Optional init을 선언하고, 다음과 같은 조건을 선언합니다.
init?(name: String, linenumber: Int) {
//조건1
if (1...8).contains(linenumber) == false {
return nil
}
//조건2
if name.count == 0 {
return nil
}
self.name = name
self.linenumber = linenumber
}
}
// 인스턴스 생성하면서 초기값을 할당해줍니다.
let Transportation1: SubwayStation? = SubwayStation(name: "Seoul", linenumber: 1)
let Transportation2: SubwayStation? = SubwayStation(name: "ShinNonhyeon", linenumber: 9)
let Transportation3: SubwayStation? = SubwayStation(name: "", linenumber: 2)
// init에서 할당한 값을 출력합니다.
print(Transportation1)
print(Transportation2)
print(Transportation3)
// =====출력결과=====
// Optional(StudyTest.SubwayStation)
// nil
// nil
이 코드는 다음과 같은 조건을 가지고 있습니다.
(1) Optional init을 선언하고,
(2) linenumber가 1~8호선 사이가 아니라면 nil을 리턴
(3) name이 공백일 경우 nil을 리턴
때문에 출력결과를 보면 9호선인 신논현역과, 2호선이지만 이름이 없는 역은 출력결과에 nil이 리턴되어 있습니다.
(온전한 형태로 들어가있는 Tranportation1은 왜 저런 결과가 나왔지는 조금 더 공부해보고 작성해보도록 하겠습니다.)
6. deinitializer
deinit의 가장 큰 특징은
(1) class 타입에서만 구현할 수 있고,
(2) 인스턴스가 해제되는 시점이 ARC(Automatic Reference Counting) 규칙에 따른다는 것입니다.
// 5. deinit 사용
class SubwayStation {
// 기본값을 할당하지 않고, 변수타입만 선언합니다.
var name: String
var linenumber: Int
// init
init(name: String, linenumber: Int) {
self.name = name
self.linenumber = linenumber
}
// deinit
deinit {
print("Hi, deinit")
}
}
// init을 호출합니다.
var Transportation: SubwayStation? = SubwayStation(name: "Seoul", linenumber: 1)
// deinit을 호출합니다.
Transportation = nil
//=====출력결과=====
// Hi, deinit
변수로 선언된 Transportation은 SubwayStation의 인스턴스 객체라는건 아시겠죠?
이렇게 생성 이후 변수에 nil을 할당하는 것이 해당 클래스의 인스턴스 메모리를 해제하는 방식 중 하나라고 하네요.
그래서 nil을 할당하면 deinit이 호출되어 위에 구현해놓은 print() 함수가 호출되어,
출력결과에 Hi, deinit이 나오는 것을 볼 수 있습니다.
간단히 정리해보겠습니다.
init(initializer)
인스턴스를 생성하는 / 초기화와 동시에 프로퍼티의 값을 할당하고 싶을 때 사용합니다.
deinit(deinitializer)
(1) 인스턴스가 소멸될 때
호출되는 인스턴스가 해제되는 시점에 해야할 일을 구현,
(2) 매개변수를 지닐 수 없고, 자동으로 호출되기 때문에 사용자가 직접 호출할 수 없습니다.
(3) 클래스 타입에만 구현가능하며,
(4) 메모리가 해제되는 시점은 ARC 규칙에 따라 결정됩니다.
꼭 알아야할 내용에 대해 포스팅해보았습니다.
오류가 있거나, 피드백이 있다면 언제든지 댓글로 알려주세요!
감사합니다 :)