본문 바로가기

스폰지밥으로 공부하는 swift/swift 문법

[값타입 vs 참조타입]을 '집게리아 레시피'로 이해 해보좌

728x90

1. 값 타입(Value Type)과 참조 타입(Reference Type)의 차이:

집게사장이 집게리아의 레시피를 복사하려고 합니다. 이 때 레시피를 복사하는 방법에 따라 값 타입과 참조 타입이 나뉩니다.

값 타입

값 타입은 집게사장이 레시피를 그대로 메모지에 옮겨 다른 곳에 붙이는 것과 같아요. 메모지에 옮겨적은 레시피를 수정해도 원본 레시피 북에는 영향을 주지 않죠. Swift에서는 구조체(Struct)와 열거형(Enum)이 값 타입입니다.

struct Recipe {
    var name: String
    var ingredients: [String]
}

var originalRecipe = Recipe(name: "Krabby Patty", ingredients: ["Bun", "Patty", "Pickle"])
var copiedRecipe = originalRecipe
copiedRecipe.name = "Squidward's Patty"

print(originalRecipe.name) // "Krabby Patty"
print(copiedRecipe.name) // "Squidward's Patty"

참조 타입

참조타입은 집게사장이 집게리아의 메뉴 가격을 조정하는 것과 같아요. 보통 식당에서 메뉴 가격을 바꾸면 메뉴판, 키오스크, 포스기 모든 곳에서 가격을 바꿔야겠죠? Swift에서는 클래스(Class)가 참조 타입이에요.

class Menu {
    private var name: String
    private var price: Int

    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }

    func setPrice(newPrice: Int) {
        self.price = newPrice
    }

    func printMenu() {
        print("\(name): \(price)원")
    }
}

let originalMenu = Menu(name: "집게버거", price: 1000)
let copiedMenu = originalMenu
copiedMenu.setPrice(newPrice: 2000)

originalMenu.printMenu() // "집게버거: 2000원"
copiedMenu.printMenu() // "집게버거: 2000원"

2. 그래서 값타입과 참조타입의 차이를 왜 알아야하나요?

이 차이를 알아야 하는 이유는 프로그래밍을 할 때 데이터를 안전하고 효율적으로 다루는데에 큰 영향을 미치기 때문입니다.
값 타입은 원본 데이터를 보호하고 참조 타입은 데이터를 효율적으로 공유할 수 있어요.

3. 각각의 장단점

값 타입의

데이터의 안정성을 보장한다는 것입니다. 복사본을 수정해도 원본에 영향을 주지 않기 때문에 예기치 않은 부작용(Side-effect)을 방지할 수 있습니다.이러한 특성은 한 스레드에서 데이터를 변경해도 다른 스레드의 데이터가 영향을 받지 않기 때문에 멀티 스레드 환경에서 특히 중요해요.

값 타입의 단점

크기가 큰 데이터를 복사할 때 메모리를 많이 차지한다는 점입니다. 매번 복사를 하기 때문에 메모리 사용량이 늘어나고, 이는 성능에 영향을 줄 수 있어요. 따라서 데이터의 크기가 크거나 복잡한 경우에는 참조 타입의 사용을 고려해볼 수 있어요.

참조 타입의 장점

참조 타입의 주요 장점은 '메모리 효율성'이에요. 참조 타입은 데이터의 주소를 참조하기 때문에, 동일한 데이터에 대한 여러 참조를 만들어도 추가 메모리가 거의 필요하지 않습니다. 이는 메모리를 절약하고, 특히 크기가 큰 데이터를 다룰 때 유용하겠죠.

참조 타입의 단점

참조타입은 복사본을 수정하면 원본에도 영향을 주기 때문에 예기치 않은 부작용을 초래할 수 있어요.


4. 그러면 어떨 때 사용해야 할까요?

값 타입(Value Type) 사용해야 할 때

  1. 데이터의 불변성이 중요한 경우: 값 타입은 원본 데이터의 안정성을 보장하기 때문에 불변성이 중요한 경우에 사용됩니다. 여러 스레드에서 동시에 접근하는 데이터를 관리할 때 값 타입을 사용하면 데이터의 일관성을 유지할 수 있어요.
  2. 작은 데이터 구조를 다루는 경우: 값 타입은 데이터를 복사하여 사용하기 때문에 작은 데이터 구조를 다루는 데 적합합니다. 숫자, 문자열, 날짜, 배열 등의 데이터를 다룰 때 값 타입을 사용하면 효율적입니다.

참조 타입(Reference Type) 사용 해야 할 때

  1. 공유 상태를 관리해야 하는 경우: 참조 타입은 데이터를 참조하기 때문에 여러 인스턴스 간에 공유 상태를 관리하는 데 사용됩니다. 여러 화면에서 동일한 데이터를 참조하고 수정해야 하는 경우 참조 타입을 사용할 수 있습니다.
  2. 큰 데이터 구조를 다루는 경우: 참조 타입은 메모리를 효율적으로 사용할 수 있기 때문에 큰 데이터 구조를 다루는 데 적합합니다. 크기가 큰 이미지나 비디오 파일 등 고용량을 다루는 경우 참조 타입을 사용하면 메모리를 절약할 수 있어요.
  3. 상속이 필요한 경우: Swift에서는 클래스만이 상속이 가능하므로 상속을 활용해야 하는 경우 참조 타입을 사용합니다.

마지막으로 메모리 관점에서 보면 어떨까요?


5. 메모리에서 본 값 타입(Value Type)과 참조 타입(Reference Type)

값 타입(Value Type)과 참조 타입(Reference Type)의 차이를 이해하려면 메모리 관점을 살펴봐야 해요. 컴퓨터 메모리는 스택(stack)과 힙(heap) 두 부분으로 나뉘는데, 이 둘은 각기 다르게 동작하거든요.

스택(Stack)

스택은 메모리의 일부로, 함수가 호출될 때 그와 관련된 정보를 저장하는 데 사용해요. 함수가 호출되면 그 함수의 매개변수와 지역 변수가 스택에 푸시(push)되고, 함수 호출이 끝나면 그 함수의 스택 프레임은 팝(pop)되어 메모리에서 사라져요. 이런 특성 덕분에 스택 메모리는 자동으로 관리되죠.

힙(Heap)

힙은 객체가 생성될 때마다 메모리를 동적으로 할당받는 영역이에요. 이 메모리는 짧은 생명주기의 지역 변수와 달리 오래동안 메모리에 머물 수 있어요. 그러나 이런 특성 때문에 메모리 누수 문제가 발생할 수 있으므로, 힙 메모리는 가비지 컬렉터나 자동 참조 카운팅(Automatic Reference Counting, ARC) 같은 메모리 관리 기법을 통해 따로 관리해야 해요.

값 타입(Value Type)과 스택 메모리

값 타입은 스택 메모리에 저장돼요. 값 타입의 변수나 상수에 값이 할당되면, 그 값은 스택에 푸시되어 저장되고, 이 변수나 상수의 생명주기가 끝나면, 그 값은 스택에서 팝되어 메모리에서 제거되죠. 이런 과정은 모두 자동으로 이루어지기 때문에, 프로그래머가 따로 메모리를 관리할 필요가 없어요.

참조 타입(Reference Type)과 힙 메모리

참조 타입은 힙 메모리에 저장돼요. 참조 타입의 인스턴스가 생성되면, 그 인스턴스는 힙에 할당된 메모리 공간을 차지하게 되고, 이 인스턴스를 참조하는 변수나 상수는 스택에 저장되죠. 이 변수나 상수의 생명주기가 끝나도, 힙에 저장된 인스턴스는 계속 메모리를 차지하게 돼요. 따라서 참조 타입의 메모리 관리는 Swift의 ARC를 통해 이루어져요.

struct KrabbyPattyRecipe {
    var secretIngredient: String
}

class KrustyKrab {
    var menu: KrabbyPattyRecipe

    init(menu: KrabbyPattyRecipe) {
        self.menu = menu
    }
}

위 코드를 보면 KrabbyPattyRecipe는 값 타입인 구조체고, KrustyKrab는 참조 타입인 클래스에요. KrabbyPattyRecipe 인스턴스는 함수 호출이 종료되면 자동으로 메모리에서 사라지지만, KrustyKrab 인스턴스는 ARC에 의해 메모리 관리가 이루어져요.

6. ARC(Automatic Reference Counting)

Swift는 참조 타입의 인스턴스가 더 이상 필요 없게 되면 해당 인스턴스를 메모리에서 해제하는 것을 보장하기 위해 ARC를 사용해요.

ARC는 인스턴스가 처음 생성될 때 메모리를 할당하고, 이 인스턴스에 대한 참조가 더 이상 없게 되면 해당 인스턴스를 메모리에서 해제해요. 이를 위해 인스턴스에 대한 참조를 '강한 참조(strong reference)'라고 부르고, 이 참조의 수를 '참조 카운트(reference count)'라고 합니다. 참조 카운트가 0이 되면 ARC는 더 이상 해당 인스턴스가 필요 없다고 판단하고 메모리에서 해제해요.

let secretRecipe = KrabbyPattyRecipe(secretIngredient: "Love")
let krustyKrab = KrustyKrab(menu: secretRecipe)

위 코드에서 krustyKrab 인스턴스가 생성되면, KrustyKrab 인스턴스에 대한 참조 카운트가 1이 돼요. krustyKrab 변수가 더 이상 KrustyKrab 인스턴스를 참조하지 않게 되면, ARC는 참조 카운트를 감소시키고 참조 카운트가 0이 되면 해당 인스턴스를 메모리에서 해제해요.

이렇게 스택과 힙, 그리고 ARC는 Swift의 메모리 관리와 밀접한 관련이 있어요. 이를 이해하면 값 타입과 참조 타입을 더 효과적으로 사용할 수 있어요.

7. 한줄 요약:

값 타입은 원본을 보호하고, 참조 타입은 메모리를 효율적으로 사용합니다. 프로그래밍을 할 때 데이터를 안전하고 효율적으로 다루려면 꼭 공부해야하는 개념입니다!