-Today's Learning Content-

  • init & deinit
  • 메모리 구조

1. 인스턴스의 생성과 소멸(init, deinit)

개념정리

  1. init
    init이란 생성자라고 하며 프로퍼티에 값이 없거나, 새로 초기화를 시켜주기 위해 사용한다.
    struct에서는 Memberwise Initializer(멤버와이즈 이니셜라이저)를 자동으로 생성하기 때문에 필수로 구현할 필요는 없지만, class의 경우 프로퍼티의 기본값이 없다면 반드시 init을 선언해 주어야 한다.
  2. deinit
    인스턴스가 소멸(해제)될 때 호출되는 코드로 오직 class에서만 사용할 수 있다.
    deinit은 매개변수를 가질 수 없고, 인스턴스가 소멸할 때 자동으로 호출되기 때문에 임의적으로 호출할 수 없다.

1) 프로퍼티 초기값

Swift에서는 인스턴스가 선언될 때 모든 프로퍼티가 초기화되어야 한다.
때문에 프로퍼티가 기본값을 가지고 있지 않다면 init 선언자를 통해 초기화를 시켜줘야 한다.

class Person { // Error: 초기화되지 않은 값이 있기 때문에 init을 생성해야 함
	var name: String // 기본값 없음
    var age: Int = 20
}
let person1 = Person() // Error: Person의 name 값이 초기화 되지 않음

위에서는 class에 기본값을 가지지 않은 프로퍼티가 있기 때문에 오류가 발생한다.
이를 해결하려면 name 프로퍼티에 기본값을 할당하거나 init을 통해 초기화를 할 수 있도록 해야한다.

class Person {
	var name: String // 기본값 없음
    var age: Int = 20
    
    // init을 통해 인스턴스 선언시 초기화
    init(name: String) {
    	self.name = name
    }
}
let person1 = Person(name: "crois")

혹은 프로퍼티를 옵셔널 값으로 설정하면 초기화를 진행하지 않아도 된다.

class Person {
	var name: String? // 옵셔널 값
    var age: Int = 20
}
let person1 = Person() // Person의 name이 옵셔널이기 때문에 초기화하지 않아도 오류가 발생하지 않음

2) 다양한 초기화 방법

init을 사용한 초기화를 사용할 때는 init을 여러개 생성하여 다양한 초기화 방법을 제공할 수 있다.
이 때, 모든 프로퍼티가 초기화되어야 하기 때문에 프로퍼티는 옵셔널 값이거나 기본값이 있을 경우에만 사용할 수 있다.

class Person {
	var name: String? // 옵셔널 값
    var age: Int = 20
    var nickName: String
    
    // 1번 초기화 방법(전체 초기화)
    init(name: String, age: Int, nickName: String) {
    	self.name = name
        self.age = age
        self.nickName = nickName
    }
    
    // 2번 초기화 방법(일부 초기화)
    // name은 옵셔널 값이기 때문에 꼭 초기화 해 줄 필요는 없다
    init(nickName: String) {
    	self.nickName = nickName
    }
    
    // 사용 불가능한 초기화 방법
    // init(name: String, age: Int) { // Error: 초기화되지 않는 값이 있음 - nickName
    	// self.name = name
        // self.age = age
    // }
}
// 1번 초기화로 선언
let person1 = Person(name: "crois", age: 30, nickName: "unknown")
// 2번 초기화로 선언
// 이 경우 person2의 name은 nil 이 된다.
let person2 = Person(nickName: "sparuta")
실제 코드 구현시 힌트표시

3) 실패 가능한 init

인스턴스를 초기화할 때 init의 매개변수 값이 잘못되어 초기화에 실패하고 인스턴스 생성에 실패할 수도 있다.
이러한 실패 가능성이 있는 이니셜라이저를 실패 가능한 이니셜라이저라고 하고 init? 이라는 옵셔널 타입으로 선언한다.

class Person {
	var name: String = "crois"
    var age: Int
    var nickName: Stirng?
    
    // 매개변수로 들어오는 값이 조건과 다른 경우 nil 반환
    // 실패 가능한 이니셜라이저
    init?(age: Int) {
    	if (age < 0) || (age >= 100) {
        	return nil
        } else {
        	self.age = age
        }
    }
}

let person1 = Person(age: 120) // Error: 조건값을 벗어나기 때문에 nil 반환. 인스턴스 생성 실패

4) deinit

deinitclass에서만 호출할 수 있는 코드로, 인스턴스의 소멸(해제)시 자동 호출된다.
class에서만 사용 가능한 이유는 아래 메모리구조에서 설명하도록 하겠다.

deinit은 인스턴스가 해제될 때 해야할 일을 지정해줄 수 있다.
자동으로 호출되므로 임의로 호출할 수 없으며, 인스턴스가 해제되는 시점은 ARC(Automatic Reference Counting)의 규칙에 의해 결정된다.

class Person {
	var name: String = "crois"
    var age: Int
    var nickName: Stirng?
    
    init(age: Int) {
    	self.age = age
    }
    
    // 인스턴스가 해제될 때 호출될 코드
    deinit {
    	print("\(name)이/가 떠났습니다.")
    }
}

let person1 = Person(age: 20)
person1 = nil
// 출력 - crois이/가 떠났습니다.

2. 메모리 구조

1. 프로세스

프로세스란?

  • 실행중인 프로그램의 인스턴스
  • 운영체제에서 앱의 실행을 관리하기 위해 생성
  • 프로세스는 자신만의 메모리 영역을 할당받고 실행상태를 관리함

2. 메모리 구조

프로세스가 생성되었을 때 해당 프로세스의 독립적인 메모리 영역을 할당받는다.
메모리 영역에는 크게 Code, Data, Heap, Stack 의 4가지 영역이 존재한다.

a. Code

Code영역은 코드를 컴파일하여 기계어로 변환된 값을 저장하는 곳으로, 우리가 작성한 코드들이 모두 이곳에 저장된다.
CPU는 이 영역에 저장된 코드를 읽고 해당 작업을 처리한다. Code영역은 프로그램 실행과 동시에 메모리에 할당되고 프로그램이 종료되면 메모리에서 해제된다.
이 영역은 Read-Only(읽기 전용)이기 때문에 프로그램 실행 중에는 변경할 수 없다.

b. Data

Data영역은 전역 변수정적(static) 변수가 저장되는 메모리 영역이다. Code영역과 마찬가지로 프로그램 실행 시 메모리에 할당되며, 종료 시 메모리에서 해제된다.
프로그램 실행 중에도 변수는 값이 변동될 수 있기 때문에 Read-Write(읽기-쓰기 가능)의 특징을 가진다.

c. Heap

Heap영역은 class 인스턴스나 클로저와 같은 참조 타입(Reference Type)의 데이터가 할당되는 동적 메모리 영역이다. 런타임에 의해 메모리의 크기가 결정되고, 확정되지 않은 크기의 데이터가 저장된다.
ARC에 의해 메모리 관리가 자동으로 이루어지지만, 메모리 누수 등을 방지하기 위해 별도로 관리가 필요하다.
Heap영역은 상대적으로 메모리에 할당되고 해제되는 속도가 느린 특징을 가지고 있다.

위에서 class에서만 deinit을 사용할 수 있다고 하였는데, 그 이유가 클래스는 Heap 메모리 영역에 저장되고, 이 영역만 ARC에 의해 관리되기 때문이다. structenum은 값 타입(Value Type)으로 대부분 Heap영역이 아닌 Stack영역에 저장되고, ARC에 의한 관리가 이루어지지 않는다.

d. Stack

Stack영역은 함수 호출시 생성되는 지역변수, 매개변수, 리턴값 등이 저장되는 메모리 영역이다. 일반적으로 값 타입(Value Type)의 데이터가 저장된다.
함수가 종료되면 자동으로 메모리에서 해제되며, 메모리 할당 및 해제의 속도가 Heap영역에 비해 빠르다. 또, Heap영역과는 다르게 컴파일 시점에 해당 영역의 크기가 결정된다.
Stack영역은 크기가 제한되어 있기 때문에 이를 초과하면 스택 오버플로우(Stack Overflow) 오류가 발생하여 프로그램이 종료될 수 있으므로 주의가 필요하다.

3. 참조 타입과 값 타입

참조 타입(Referance Type)

  • 데이터를 직접 저장하지 않고 메모리주소를 저장하고 전달하여 사용
  • 참조 타입의 인스턴스를 다른 변수나 상수에 할당하거나 메소드의 파라미터로 전달할 때, 저장하고 있는 인스턴스의 실제 주소값을 전달
  • 주소값을 전달하기 때문에 값을 변경하면 원본의 값도 변경됨
  • ARC에 의해 자동으로 메모리가 관리됨
  • 참조카운팅을 통해 메모리 해제 시점이 결정됨

값 타입(Value Type)

  • 데이터를 직접 저장하고 복사하여 전달
  • 값 타입의 인스턴스를 다른 변수나 상수에 할당하거나 메소드의 파라미터로 전달할 때, 실제 값의 복사본을 생성하여 전달(원본값 변화 X)
  • 원본의 값이 변경되지 않기 때문에 멀티스레드 환경에서 안전하게 사용 가능

a. class의 인스턴스가 let일 때도 프로퍼티의 변경이 가능했던 이유

이전 글에서 class의 인스턴스는 상수(let)으로 선언해도 프로퍼티의 값을 변경할 수 있다고 했는데, 그 이유는 class가 참조 타입으로 원본의 주소 값을 저장하기 때문이다.
때문에 struct는 값을 복사해서 전달하기 때문에 인스턴스를 상수로 선언하면 프로퍼티를 변경할 수 없는 것이다.

class Person {
	var name: String
    var age: Int
    
    init(name: String, age: Int) {
    	self.name = name
        self.age = age
    }
}
// 인스턴스를 상수로 선언
// 인스턴스에 원본(타입)의 주소값을 저장
let person = Person(name: "crois", age: 20)
let person2 = person // person과 같은 클래스 공유

// 프로퍼티 값 변경 시도 - 에러 없음
// 인스턴스가 원본의 주소값을 통해 값을 변경하기 때문에 문제 없음
person.age = 30

print(person.age) // 30
print(person2.age) // 30
// person2는 person과 같은 주소를 공유하기 때문에 
// person의 프로퍼티가 바뀌면 같이 바뀜
struct Animal {
	var name: String
    var age: Int
}
let animal = Animal(name: "Dog", age: 5)
animal.age = 10 // Error - animal은 상수라서 변경 불가

var animal1 = Animal(name: "Cat", age: 2)
var animal2 = animal1

animal1.age = 1

print(animal1.age) // 1
print(animal2.age) // 2 
// animal2는 animal1의 값을 복사하여 전달 받았기 때문에 
// animal1의 값을 바꿔도 영향을 받지 않는다.

이미지로 보면 아래와 같다.


-Today's Lesson Review-

오늘은 강의를 듣고, 과제로 만든 Calculatoer를 수정하는 시간을 가졌다.
지난 강의와 달리 이번에는 처음 학습하는 과정(메모리 등)이 포함되어 있었기에
생각보다 유익한 시간이었다.
지금까지는 그냥 이건 된다, 안된다 등으로만 생각했던 지식들을 메모리와 연관지어 수업을 들으며
어째서 어떤 상황에서는 되고, 어떤 상황에서는 안되는지 확실히 알 수 있었다.
profile
이유있는 코드를 쓰자!!

2개의 댓글

comment-user-thumbnail
2024년 10월 29일

와 init도 옵셔널이 있군요 배워갑니다!

1개의 답글