The Swift Language Guide(한국어) - 클래스와 구조체
NewClass 클래스를 작성한다.
class NewClass {
var count = 0
}
class1을 생성하고 class1을 이용해 class2를 생성한다.
var class1 = NewClass()
var class2 = class1
class2의 count값을 변경하였지만 class1의 count값도 변경된 것을 확인할 수 있다. 클래스는 참조 타입이기 때문이다.
class2.count = 1
print(class1.count) // 1
NewStruct 구조체를 작성한다.
struct NewStruct {
var count = 0
}
struct1을 생성하고 struct1을 이용해 struct2를 생성한다.
var struct1 = NewStruct()
var struct2 = struct1
struct2의 count값을 변경하였지만 struct1의 count값은 변경되지 않았다. 구조체는 값 타입이기 때문이다.
struct2.count = 1
print(struct1.count) // 0
NewClass에 소멸자를 추가한다.
class NewClass {
var count = 0
deinit {
print("할당 해제")
}
}
참조 카운트의 기본값은 2이다. classARC2는 classARC1도 참조하고 있기에 참조 카운트가 1 추가된다.
var classARC1: NewClass? = NewClass()
print(CFGetRetainCount(classARC1)) // 2
var classARC2: NewClass? = classARC1
print(CFGetRetainCount(classARC2)) // 3
참조되는 모든 값들을 해제하였기 때문에 deinit이 실행된다. "할당 해제"가 출력된 것을 확인할 수 있다.
classARC1 = nil
print(CFGetRetainCount(classARC2)) // 2
classARC2 = nil // deinit 실행됨
클래스 StrongRefClassA와 StrongRefClassB를 작성한다.
class StrongRefClassA {
var classB: StrongRefClassB?
deinit {
print("A 할당 해제")
}
}
class StrongRefClassB {
var classA: StrongRefClassA?
deinit {
print("B 할당 해제")
}
}
클래스 classA와 classB를 생성한다. 참조 카운트의 기본값인 2가 출력된다.
var classA: StrongRefClassA? = StrongRefClassA()
var classB: StrongRefClassB? = StrongRefClassB()
print(CFGetRetainCount(classA)) // 2
print(CFGetRetainCount(classB)) // 2
classA의 classB는 classB를 참조하도록 하고, classB의 classA는 classA를 참조하도록 한다. 참조 카운트가 증가한다.
classA?.classB = classB
classB?.classA = classA
print(CFGetRetainCount(classA)) // 3
print(CFGetRetainCount(classB)) // 3
classA와 classB를 지웠지만 deinit이 실행되지 않았다. 이렇게 되면 메모리 누수가 발생하게 된다.
classA = nil
print(CFGetRetainCount(classB?.classA)) // 2
classB = nil // deinit 실행되지 않음
weak 참조를 사용하면 retain cycle을 방지할 수 있다.
class StrongRefClassA {
weak var classB: StrongRefClassB?
deinit {
print("A 할당 해제")
}
}
class StrongRefClassB {
weak var classA: StrongRefClassA?
deinit {
print("B 할당 해제")
}
}
var classA: StrongRefClassA? = StrongRefClassA()
var classB: StrongRefClassB? = StrongRefClassB()
print(CFGetRetainCount(classA)) // 2
print(CFGetRetainCount(classB)) // 2
classA?.classB = classB
classB?.classA = classA
print(CFGetRetainCount(classA)) // 2
print(CFGetRetainCount(classB)) // 2
classA = nil // deinit 실행됨
classB = nil // deinit 실행됨
이러한 특징들 때문에 구조체와 클래스는 메모리에 저장되는 위치가 다르다. 구조체는 언제 생기고 사라질 지 컴파일 단계에서 알 수 있기에 메모리의 stack 공간에 할당된다. 반면 클래스는 참조가 어디서 어떻게 될 지 모르기에 Heap 공간에 할당된다.
Stack은 LIFO(Last In First Out)로 가장 마지막에 들어간 객체가 가장 먼저 나오는 자료구조이다. 하나의 명령어로 메모리를 할당, 해제할 수 있다. 또한 컴파일 단계에서 언제 생성되고 해제되는지 알 수 있는 구조체와 같은 값 타입이 저장되게 된다.
스레드들은 각각 독립적인 Stack 공간을 가지고 있기 때문에 상호 배제를 위한 자원이 필요하지 않는다. 즉 스레드로부터 안전하다. 이러한 특징 때문에 Stack의 값을 사용하는 것이 Heap의 값을 사용하는 것보다 빠르다.
*스레드란 프로세스의 작업흐름의 단위를 말한다.
*컴파일이란 사람이 보기 편하게 만든 소스코드를 컴퓨터가 이해할 수 있는 기계어 구조로 변환하는 과정을 의미한다.
Heap에는 컴파일 단계에서 생성돠 해제를 알 수 없는 참조 타입의 객체가 할당된다. 클래스 객체가 힙에 할당된다. Heap은 참조 계산도 해야 하기에 메모리 할당과 해제가 하나의 명령어로 처리되지 않는다.
Heap은 스레드가 공유하는 메모리 공간이기에 스레드로부터 안전하지 않는다. 이를 관리하기 위한 lock과 같은 자원도 필요하게 되고 이는 곧 오버 헤드로 이어지게 된다.
*오버헤드란 어떤 처리를 위한 간접적인 처리 시간, 메모리 등을 의미한다.
단순한 데이터 값을 보유하기 위해서는 구조체를 쓴다. 그리고 메모리의 스택은 크기가 크지 않기에 작은 값을 갖는 데이터를 처리할 때 구조체를 사용한다. 반면 Object-C와 상호 운용성을 원할 때는 클래스를 사용한다.