티스토리 뷰
1. Initializers
생성자에 대해 알아보겠습니다.
열거형, 구조체, 클래스는 모두 설계도입니다. 설계도를 기반으로 생성한 것을 인스턴스라고 합니다. 다른 언어에서는 객체에 한해 인스턴스라는 용어를 사용하지만 Swift 에서는 구분 없이 사용합니다. 새로운 인스턴스를 생성하는 것을 초기화라고 합니다. 인스턴스의 초기화를 담당하는 것이 바로 initializer 입니다.
초기화는 모든 속성을 기본 값으로 초기화해서 인스턴스를 기본 상태로 만드는 것이 목적입니다. 인스턴스가 정상적으로 초기화 되었다는 것은 initializer 의 실행이 완료되었을 때, 모든 속성이 기본 값을 가지고 있다는 뜻입니다. 만약 기본 값이 없는 속성이 존재한다면 초기화에 실패하고 인스턴스는 생성되지 않습니다.
기본적인 초기화 방법을 알아보겠습니다. 인스턴스를 생성하기 위해서는 initializer 를 호출해야 합니다. 따라서 모든 속성이 기본 값을 가지고 있고 initializer 를 직접 구현하지 않은 경우에는 컴파일러가 default initializer 를 자동으로 생성해줍니다. 만약 initializer 를 직접 구현하는 경우에는 default initializer 가 제공되지 않습니다.
class Position {
var x = 0.0 // 속성이 주로 동일한 값으로 초기화 된다면 직접 값을 지정하는 방식으로 초기화
var y: Double
var z: Double? // optional 속성의 경우 값을 지정하지 않으면 자동으로 nil 로 초기화
// 파라미터를 통해 기본 값을 지정할 때는 주로 init() 을 사용하여 초기화
init() {
y = 0.0
}
}
let p = Position()
초기화의 문법과 호출 방법은 다음과 같습니다.
// Initializer Syntax
// 구현 문법
init(parameters) {
initialization
}
// 호출 방법
TypeName(parameters)
클래스로 객체를 구현하고 initializer 를 구현해보겠습니다.
class SizeObj {
var width = 0.0
var height = 0.0
init(width: Double, height: Double) {
self.width = width
self.height = height
}
convenience init(value: Double) {
// width = value
// height = value
// initializer Delegation
// : initializer 에서 다른 initializer 를 호출
self.init(width: value, height: value)
}
}
이번에는 구조체로 구현해 보겠습니다.
struct SizeValue {
var width = 0.0
var height = 0.0
}
let s = SizeValue()
SizeValue(width: 1.2, height: 3.4)
값 형식인 구조체의 경우는 Memberwise Initializer 라는 것을 제공합니다. Memberwise Initializer 는 initializer 를 구현하지 않았을 때 자동으로 제공되며 모든 속성의 값을 초기화 할 수 있도록 속성 이름과 동일한 파라미터를 받습니다.
2. Class Initializers
클래스에서 구현할 수 있는 initializer 는 Designated Initializer 와 Convenience Initializer 로 구분됩니다.
Designated Initializer (지정 생성자)
: 클래스가 가진 모든 속성을 초기화 하는 메인 initializer 입니다. 클래스에서는 initializer 의 실행이 종료되기 전에 super 클래스의 designated initializer 가 실행되어야 합니다. 이것을 initializer Delegation 이라고 합니다. 클래스에서 구현할 수 있는 designated initializer 의 수에는 제한이 없습니다. 하지만 대부분의 경우 하나만 구현합니다.
init(parameters) {
initialization
}
Convenience Initializer (간편 생성자)
: 다양한 초기화를 구현하기 위한 유틸리티 성격의 initializer 입니다. 반드시 모든 속성을 초기화 해야 하는 것은 아닙니다. 보통 필요한 속성만 초기화를 진행하고 클래스 내 다른 initializer 를 호출하여 나머지 속성의 초기화를 진행합니다. super class 의 designated initializer 를 직접 호출할 수 없습니다. 반드시 동일한 클래스에 있는 initializer 를 호출해서 최종적으로 designated initializer 가 호출될 수 있도록 구현해야 합니다.
convenience init(parameters) {
initialization
}
간단한 구현 예를 살펴보겠습니다.
class Position {
var x: Double
var y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
convenience init(x: Double) {
self.init(x: x, y: 0.0)
}
}
다른 멤버들을 조건 없이 상속되지만 initializer 는 상속되는 조건이 상대적으로 까다롭습니다. 기본적으로 super class 에서 구현된 initializer 는 sub class 로 상속되지 않습니다. Swift 는 두가지 규칙에 따라 initializer 를 상속합니다.
1. sub class 의 모든 값이 기본 값으로 초기화 되어 있고, designated initializer 를 직접 구현하지 않았다면
super class 의 모든 designated initializer 가 상속됩니다.
2. sub class 가 모든 designated initializer 를 상속 받았거나 overriding 했다면 모든 convenience initializer 가 상속됩니다.
아래의 경우 Rectagle 클래스는 Figure 클래스의 Designated Intializer 를 상속받게 됩니다.
class Figure {
var name: String
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Rectangle: Figure {
var width: Double = 0.0
var height: Double = 0.0
}
생성자 또한 overriding 이 가능합니다,
class Figure {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "unknown")
}
func draw() {
print("draw \(name)")
}
}
class Rectangle: Figure {
var width: Double = 0.0
var height: Double = 0.0
init(name: String, width: Double, height: Double) {
self.width = width
self.height = height
super.init(name: name)
}
override init(name: String) {
width = 0
height = 0
super.init(name: name)
}
// 항상 동일한 클래스에 있는 다른 initializer 를 호출하고 super class 의 initializer 를 호출하는 것은 불가능합니다.
// 그래서 overriding 이라는 개녕이 적용되지 않습니다.
convenience init() {
self.init(name: "unknown")
}
}
3. Required Initializer
필수 생성자로 문법은 다음과 같습니다.
required init(parameters) {
initialization
}
class Figure {
var name: String
// sub class 에서 반드시 해당 initializer 를 직접 구현해야 합니다.
required init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
// sub class 의 모든 속성이 초기 값을 가지고 있고 initializer 를 구현하지 않은 경우
// super class 의 intializer 가 상속되기 때문에
// required initializer 가 상속되는 경우에는 직접 구현하지 않아도 됩니다.
class Rectangle: Figure {
var width = 0.0
var height = 0.0
init() {
width = 0.0
height = 0.0
super.init(name: "unknown")
}
// sub class 에서 required initializer 를 구현할 때는
// super class 의 required initializer 와 완전히 동일한 형태로 구현합니다.
// 이렇게 하는 이유는 Rectangle 을 상속 받는 다른 클래스들에서도
// required initializer 를 구현하도록 다시 강제화 하기 위해서 입니다.
required init(name: String) {
width = 0.0
height = 0.0
super.init(name: name)
}
}
4. Initializer Delegation
초기화 코드에서 최대한 중복을 제거하고 모든 속성을 효율적으로 초기화 하기 위해 사용합니다. 값 형식과 참조 형식에서 서로 다른 방식으로 구현됩니다.
값 형식은 상속이 불가능하고 initializer 종류가 하나이기 때문에 상대적으로 단순합니다.
struct Size {
var width: Double
var height: Double
init(w: Double, h: Double) {
width = w
height = h
}
init(value: Double) {
self.init(w: value, h: value)
}
}
Class 는 상속이 가능하고 구현할 수 있는 initializer 의 종류가 2가지이기 때문에 상대적으로 복잡합니다. 특히 상속 계층을 따라 올라가면서 모든 initializer 가 올바른 순서대로 호출되도록 구현하는 것이 매우 중요합니다.
1. Designated Initializer 는 반드시 super class 의 designated Initializer 를 호출해야 합니다. 이것을 Delegate Up 이라고 합니다. Designated Initializer는 동일한 클래스의 다른 Designated Initializer 를 호출하는 것은 불가능합니다. 이 경우 compile error 가 발생합니다.
2. Convenience Initializer 는 동일한 클래스에 있는 다른 Initializer 를 호출해야 합니다. 이것을 Delegate Across 라고 합니다. super class 에 있는 Convenience Initializer 는 동일한 클래스의 Initializer 를 호출해야 합니다. 호출할 Initializer 의 종류는 관계가 없습니다. 이는 sub class 에서도 마찬가지입니다. sub class 의 Convenience Initializer 에서 super class 의 Initializer 를 호출하는 것은 불가능합니다.
3. Convenience Initializer 를 호출했을 때 최종적으로 동일한 클래스에 있는 Designated Initializer 가 호출되어야 합니다.
위 3가지 규칙이 적용된 간단한 예를 살펴보겠습니다.
class Figure {
let name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "unknown")
}
}
class Rectangle: Figure {
var width = 0.0
var height = 0.0
init(n: String, w: Double, h: Double) {
width = w
height = h
super.init(name: n)
}
convenience init(value: Double) {
self.init(n: "rect", w: value, h: value)
}
}
class Square: Rectangle {
convenience init(value: Double) {
self.init(n: "Square", w: value, h: value)
}
convenience init() {
self.init(value: 0.0)
}
}
만약 Square 클래스의 parameter 가 없는 Convenience Initializer 를 호출한다면
(1) parameter 가 없는 convenience init() 이 호출됩니다.
(2) 다음으로 self.init(value: 0.0) 에 의해 같은 Square 클래스의 convienience init(value: Double) 이 호출됩니다.
(3) Square 클래스는 속성이 없고 Designated Initializer 가 구현되지 않았기 때문에 Rectangle 클래스의 Designated 클래스를 상속 받으며, 따라서 이 상속받은 initializer 가 다음으로 호출됩니다.
(4) 상속 받은 initializer 에서 상속 받은 속성인 width, height 가 초기화 되고 super.init(name: n) 을 통해 Figure 클래스의 Designated Initializer 를 호출합니다.
(5) 최종적으로 FIgure 클래스의 init(name: String) 을 통해 name 속성이 초기화 됩니다.
위에서 설명한 것과 같이 하위 계층의 클래스에서 상위 계층 클래스로 올라가며 속성 초기화를 마치면 (1단계) 다시 올라간 순서와 반대로 내려오면서 추가적인 작업을 수행합니다. (2단계) 다른 생성자를 호출하는 코드 앞에 쓴 코드는 1단계 과정에서 실행되고 뒤에 쓴 코드는 2단계에서 실행됩니다.
5. Failable Initializer
실패를 허용하는 initializer 로 초기화에 실패하더라도 에러가 발생하지 않습니다. 대신 nil 을 리턴하게 됩니다.
// 문법
// 초기화에 성공하면 초기화된 인스턴스가 Optional 형식으로 리턴됩니다. 반대로 초기화에 실패한다면 nil 을 리턴합니다.
init?(parameters) {
initialization
}
// 초기화에 성공한다면 인스턴스를 Non-Optional 형식으로 리턴합니다. 그리고 초기화에 실패하는 경우 crash 가 발생합니다.
init!(parameters) {
initialization
}
struct Position {
var x: Double
var y: Double
init?(x: Double, y: Double) {
guard x >= 0.0, y >= 0.0 else { return nil }
self.x = x
self.y = y
}
init!(value: Double) {
guard value >= 0.0 else { return nil }
x = value
y = value
}
}
var a = Position(x: 12, y: 34)
a = Position(x: -12, y: 0) // nil
var b: Position = Position(value: 12)
// b = Position(value: -12) // crash 발생
Non Failable Initializer 에서는 ! 가 붙은 Failable Initializer 만 호출할 수 있습니다. 하지만 초기화가 실패하는 경우 crash 가 발생하기 때문에 이런 패턴은 사용하지 않는 것이 좋습니다.
Failable Initializer 에서는 다른 Failable Initializer 를 호출해도 되고 Non Failable Initializer 를 호출해도 문제가 없습니다.
6. Deinitializer
소멸자라고 부르며 인스턴스가 메모리에서 제거되기 전에 부가적인 정리 작업을 구현하기 위해 사용됩니다.
Deinitializer 는 클래스 전용이며 하나로 제한됩니다. Deinitializer 를 직접 호출하는 문법은 제공되지 않으며 인스턴스가 메모리에서 제거되기 직전에 자동으로 호출됩니다.
Deinitializer 를 구현하지 않으면 자동으로 제공됩니다. 파일 연결이나 네트워크 연결처럼 자동적으로 정리되지 않는 리소스를 정리하거나 인스턴스 해제를 확인하는 것처럼 디버깅 용도로 활용합니다.
// 문법
// deinit {
// Deinitialization
// }
class Size {
var width = 0.0
var height = 0.0
}
class Position {
var x = 0.0
var y = 0.0
}
class Rect {
var origin = Position()
var size = Size()
deinit {
// 인스턴스는 Deinitializer 의 실행이 완료될 때까지 메모리에 유지되기 때문에 self 로 접근하는 것이 가능합니다.
// 속성에 접근하거나 메소드를 호출하는 것도 가능합니다.
print("deinit \(self)")
}
}
var r: Rect? = Rect()
r = nil
// r = nil 코드를 실행하면 Rect 인스턴스가 메모리에서 제거되며 Deinitializer 가 호출됩니다.
// Rect 인스턴스가 제거되는 시점에 속성에 저장되어 있는 다른 인스턴스도 함께 제거됩니다.
'swift > 문법' 카테고리의 다른 글
22. Swift 문법 - Protocol 1 (0) | 2020.10.26 |
---|---|
21. Swift 문법 - Extensions (0) | 2020.10.26 |
19. Swift 문법 - Inheritance and Polymorphism (0) | 2020.10.24 |
18. Swift 문법 - Method and Subscript (0) | 2020.10.24 |
17. Swift 문법 - Property (0) | 2020.10.23 |
- Total
- Today
- Yesterday
- 수학
- BFS
- CodeCommit
- sort
- Combination
- Dynamic Programming
- 조합
- java
- ionic
- search
- Baekjoon
- spring
- ECR
- string
- programmers
- CodePipeline
- 프로그래머스
- 에라토스테네스의 체
- permutation
- CodeDeploy
- EC2
- array
- Algorithm
- map
- 순열
- cloudfront
- 소수
- DFS
- AWS
- SWIFT
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |