Swift

[Swift] Value and Reference Types

yujaehui 2024. 4. 4. 22:55

값 타입(Value Type) vs 참조 타입(Reference Type)

Swift에서는 모든 데이터가 값 타입 또는 참조 타입으로 분류.

이 두 타입은 메모리에서 데이터를 저장하고 관리하는 방식이 다르며, 각각의 타입은 데이터의 복사 및 공유 방식에 큰 차이가 존재.


1. 값 타입(Value Type)

값 타입은 데이터를 복사하여 저장. 변수나 상수에 값을 할당하거나, 함수에 값을 전달할 때 새로운 복사본 생성.
따라서 값 타입을 사용하는 경우, 각각의 인스턴스는 독립적인 값을 가지며, 다른 변수에 영향을 주지 않음.

  • 대표적인 값 타입: struct, enum, tuple

1.1. 특징

  • 복사
    • 값을 할당하거나 전달할 때 값이 복사.
  • 독립성
    • 하나의 값 타입 인스턴스가 변경되어도 다른 인스턴스에 영향을 미치지 않음.
  • 성능
    • 스택(stack)에 저장되어 빠른 메모리 접근이 가능.
    • 함수가 종료되면 스택에서 메모리가 자동으로 해제

1.2. 예시

struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 10, y: 20)
var point2 = point1 // point1의 값을 point2에 복사

point2.x = 30

print(point1.x) // 10 (point1은 영향을 받지 않음)
print(point2.x) // 30
  • point1의 값이 point2로 복사되었으며, point2의 값을 변경해도 point1에 영향을 주지 않음.

1.3. 사용하는 이유

  • 값 타입은 데이터를 복사하므로, 객체의 변경으로 인한 부작용을 방지할 수 있음.
    • 불변성을 유지하며 데이터를 처리할 때 값 타입이 유용.
  • 스레드 안전성을 보장해야 하는 경우 적합.
  • Swift 표준 라이브러리의 기본 타입 (Int, Double, Bool, String 등)은 모두 값 타입.

2. 참조 타입(Reference Type)

참조 타입은 데이터를 참조로 저장하며, 여러 변수가 동일한 인스턴스를 참조할 수 있음.

참조 타입에서는 데이터를 복사하지 않고, 참조하는 메모리 주소를 공유.

따라서 하나의 참조 타입 인스턴스를 여러 곳에서 공유할 경우, 한 곳에서 인스턴스를 변경하면 모든 참조 변수에 영향을 미치게 됨.

  • 대표적인 참조 타입: class, closure

2.1. 특징

  • 참조
    • 값을 할당하거나 전달할 때 값이 복사되지 않고 참조만 전달.
  • 공유
    • 여러 변수가 동일한 인스턴스를 참조하므로, 한 곳에서 변경하면 다른 모든 참조에 영향.
  • 메모리
    • 힙(heap)에 저장되며, ARC(Automatic Reference Counting)가 메모리 관리.
    • 참조 카운트가 0이 되면 메모리가 해제.

2.2. 예시

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

var person1 = Person(name: "John")
var person2 = person1 // person1과 동일한 인스턴스를 참조

person2.name = "Alice"

print(person1.name) // Alice (person1도 영향을 받음)
print(person2.name) // Alice
  • person1과 person2는 동일한 Person 인스턴스를 참조하므로, 한 쪽을 변경하면 다른 쪽도 변경.

2.3. 사용하는 이유

  • 참조 타입은 상태를 공유해야 하는 경우 적합.
    • 예를 들어, 여러 객체가 같은 데이터나 상태를 공유하며 협력해야 할 때.
  • 클래스(class)는 상속을 지원하여 객체 지향 프로그래밍에서 중요한 역할.
  • 참조 타입은 상속, 다형성 등의 객체 지향 프로그래밍 패턴을 구현할 수 있어 복잡한 구조를 구성할 때 유용.

3. 값 타입과 참조 타입의 비교

  값 타입 (Value Type) 참조 타입 (Reference Type)
메모리 위치 스택(Stack) 힙(Heap)
복사 여부 값이 복사됨 (독립적인 값) 참조가 복사됨 (메모리 주소를 공유)
변경 영향 값이 변경되더라도 다른 변수에 영향 없음 하나의 변수를 변경하면 모든 참조 변수에 영향을 줌
사용되는 타입 struct, enum, tuple class, closure
메모리 해제 함수가 종료되면 자동으로 스택에서 해제 ARC가 참조 카운트에 따라 메모리 해제
주요 사용 사례 불변성 유지가 필요한 경우, 독립적인 값 필요 시 상속, 다형성 등 객체 지향 패턴이 필요한 경우

4. 선택 가이드

4.1. 값 타입을 선택하는 경우

  • 독립적인 데이터
    • 복사된 데이터가 서로 독립적이어야 하는 경우 값 타입을 사용.
    • 좌표값이나 사용자 정보처럼 서로 영향을 주지 않도록 하는 것.
  • 불변성
    • 불변성(immutable)을 유지해야 할 때 값 타입을 사용하면 데이터의 안전성을 높일 수 있음.
  • 경량 객체
    • 값 타입은 스택에 저장되므로 메모리 관리 오버헤드가 적고, 경량 객체에서 성능상의 이점 존재.

4.2. 참조 타입을 선택하는 경우

  • 상태 공유
    • 여러 객체가 동일한 데이터를 공유해야 할 때 참조 타입이 적합.
    • 뷰 컨트롤러 간에 데이터 모델을 공유하거나 앱의 글로벌 설정 값을 공유할 때.
  • 객체 지향 패턴
    • 상속이나 다형성 등 객체 지향 프로그래밍의 개념을 구현할 때 참조 타입이 필요하며, 특히 클래스 상속을 통해서 코드의 재사용성을 높일 수 있음.

5. 스위프트에서 값 타입과 참조 타입의 결합

Swift는 값 타입을 기본으로 하지만, 참조 타입과 결합하여 강력한 프로그래밍 패턴을 만들 수 있음.

예를 들어, 값 타입인 구조체와 참조 타입인 클래스 또는 클로저를 결합하여 효율적이고 안전한 코드 작성 가능.

struct Task {
    var title: String
    var owner: Person // Person은 참조 타입
}

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

let john = Person(name: "John")
var task = Task(title: "Learn Swift", owner: john)

task.owner.name = "Alice"
print(john.name) // Alice (참조 타입이므로 변경된 이름이 반영됨)
  • 값 타입인 Task는 독립적인 값으로 복사되지만, 참조 타입인 Person을 소유하고 있으므로 내부의 참조된 객체는 공유 됨.

6. 메모리의 4가지 영역

  • 코드 영역 (Text Segment)
    • 실행할 프로그램의 코드가 저장.
    • 함수, 메소드와 같이 실행 가능한 명령어들이 위치.
  • 데이터 영역 (Data Segment)
    • 전역 변수, 정적 변수, 상수가 저장.
    • 프로그램 시작부터 종료까지 메모리에 고정되며, 주로 프로그램 실행 동안 유지되는 전역 데이터가 이 곳에 저장 됨.
  • 힙 영역 (Heap)
    • 동적 메모리 할당이 이루어지는 공간.
    • 런타임에 필요할 때마다 메모리를 할당하고, 더 이상 필요하지 않으면 해제 됨.
    • 참조 타입인 클래스의 인스턴스와 같은 객체들이 주로 이 곳에 저장 됨.
  • 스택 영역 (Stack)
    • 함수 호출 시 지역 변수나 함수의 매개변수가 저장되는 공간.
    • 값 타입이 저장되며, LIFO(Last In, First Out) 방식으로 메모리가 관리됨.
    • 함수가 종료되면 스택에서 자동으로 메모리가 해제.

7. 메모리 관리 (ARC - Automatic Reference Counting)

참조 타입은 ARC(Automatic Reference Counting)에 의해 메모리 관리.

ARC는 참조 카운트를 추적하여 더 이상 참조되지 않는 객체가 있을 때 힙에서 해당 객체를 해제.

ARC는 자동으로 동작하므로, 프로그래머가 직접 메모리 해제를 관리하지 않아도 됨.

그러나 강한 참조 순환(Strong Reference Cycle)과 같은 문제가 발생할 수 있으므로, 적절한 메모리 관리 기법이 필요.

ARC의 동작 방식

  • 강한 참조(Strong Reference)
    • 변수가 인스턴스를 참조할 때 참조 카운트가 1 증가.
  • 참조 해제
    • 변수가 더 이상 인스턴스를 참조하지 않을 때 참조 카운트가 1 감소.
  • 메모리 해제
    • 참조 카운트가 0이 되면, 해당 객체가 힙 메모리에서 해제.