본문 바로가기

Swift

Swift - ARC(Automatic Reference Counting)

안녕하세요 오늘은 ARC에 대해 공부해보려고 합니다.

 

기본적으로 애플 공식문서를 참고하였고

틀린 내용이 있을 수 있기에 읽으시는데 참고하시길 바랍니다.

 

1. ARC란 무엇인가?

일단 메모리에 대해 알고 가야합니다.

 

기본적으로 프로그램을 실행하면 프로세스가 메모리에 적재됩니다.

 

더 자세히 설명하면

 

디스크(SSD/HDD)에 저장되어 있는 실행파일(.exe 파일)이 RAM(메모리)에 로드되고 CPU가 메모리에 올라온 코드와 데이터를 실행하는 것입니다.

 

디스크, 실행파일, CPU 등 다양한 단어들이 나오는데 오늘은 메모리에만 집중을 해보겠습니다.

 

예시로 설명하면

게임 파일을 실행하면 메모리에서 게임파일을 실행하는데 필요한 코드 등이 올라간다는 것입니다.

 

그러다 게임을 끄면 당연히 메모리에 올라가 있던 게임 파일 실행 관련 코드 등을 제거해야 합니다.

 

메모리는 용량이 한정되어 있습니다. 실제로 컴퓨터에 있는 RAM은 메모리의 한 종류이고 16GB, 32GB 등 용량이 다른 것을 알 수 있습니다.

 

따라서 메모리를 효율적으로 사용하여야 합니다.

 

기존의 C언어 기반에서는 메모리 할당과 메모리 해제를 코드로 작성해 주어야 했지만,

 

Swift에서는 ARC(Automatic Reference Couting)는 메모리 해제를 자동으로 관리해 줍니다.

 

 

2. ARC 작동 원리

그렇다면 ARC는 어떻게 작동할까요?

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

let myDog = Dog(name: "Choco")

 

이러한 코드가 있다고 가정해 봅시다.

 

Dog라는 클래스를 만들고 myDog라는 객체를 선언하였습니다.

 

이 코드를 실행하면 메모리의 힙 영역에는 Dog의 인스턴스(객체)가 할당됩니다.

 

그리고 myDog는 그 객체를 참조하는 변수입니다.

 

이번에도 예시로 설명을 하자면

 

공장에서 장난감 강아지를 생산하기 위해 설계도를 제작합니다.

 

그리고 이 설계도에 따라 장난감 강아지를 생산한 후 창고에 저장합니다.

 

우리는 이 장난감을 둔 곳을 myDog라고 하였습니다.

 

이때 설계도는 클래스, 장난감 강아지는 객체, 창고는 힙에 해당합니다.

 

그렇다면 장난감 강아지를 구매하려는 고객이 있다고 가정해 봅시다.


창고에 장난감 강아지를 보관합니다.

 

하지만 더 이상 장난감 강아지를 구매하려고 하는 사람이 없고, 구매하려고 했지만 구매를 취소했다면

 

창고에는 장난감 강아지를 보관할 필요가 없습니다.

 

이때 ARC가 자동으로 장난감 강아지를 정리하는 것입니다.

 

ARC는 객체를 예약(참조)한 사람의 수를 세고 있다가 그 수가 0이 되면 자동으로 메모리에서 제거합니다.

 

객체를 참조하는 횟수(Reference Count)가 0이 되면 ARC는 해당 객체를 자동으로 메모리에서 해제합니다.

 

 

3. 참조(Reference)

 

그렇다면 ARC는 참조 횟수를 어떻게 알까요?

 

ARC는 참조 횟수(reference count)를 가지고 있습니다.

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

let myDog1 = Dog(name: "Choco")
let myDog2 = myDog1
let myDog3 = myDog1

 

위의 코드를 조금 수정해 보았습니다.

 

Dog 클래스의 객체를 만들어 myDog1에 할당을 하였고

 

myDog2와 myDog3에 다시 할당하였습니다.

 

그렇다면 ARC는 참조 횟수를 몇이라고 저장할까요?

 

ARC를 참조 횟수를 3이라고 저장합니다.

 

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

var myDog1: Dog?
var myDog2: Dog?
var myDog3: Dog?

myDog1 = Dog(name: "Choco")
myDog2 = myDog1
myDog3 = myDog1

myDog2 = nil
myDog3 = nil

 

그렇다면 이 경우에는 참조 횟수는 몇일까요?

 

1입니다 3에서 Dog2와 Dog3가 참조를 그만뒀기 때문입니다.

 

이와 같은 참조를 강한 참조(strong reference)라고 합니다.

 

 

4. 강한 참조 순환(Strong Reference Cycles)

 

강한 참조를 사용하다 보면 문제가 생기는데,

 

객체를 사용하지 않음에도 참조 횟수가 0이 되지 않아 ARC가 객체를 제거하지 않는 상황이 발생합니다.

 

이를 강환 참조 순환(Strong Reference Cycles)라고 합니다.

 

코드를 보겠습니다.

class Dog {
    var name: String
    var cat: Cat?
    
    init(name: String) {
        self.name = name
    }
}

class Cat {
	var legs: Int
    var dog: Dog?
    
    init(legs: Int) {
    	self.legs = legs
    }
}

var dog: Dog?
var cat: Cat?

dog = Dog(name: "Choco")
cat = Cat(legs: 4)

dog!.cat = cat
cat!.dog = dog

dog = nil
cat = nil

 

이번에는 Cat 클래스를 추가해 보았습니다.

 

Dog 클래스와 Cat 클래스는 서로의 클래스를 강한 참조(Strong Reference)하고 있습니다.

 

따라서 dog와 cat 변수에 nil 값을 넣어 참조를 끊어, 각 개체를 이용하지 않더라도

 

두 클래스는 서로를 참조하고 있기에 ARC는 객체를 해제하지 못합니다.

 

이 문제를 어떻게 해결할까요?

 

 

5. 약한 참조(Weak Reference)

이 문제를 해결하기 위해 2가지 방법을 사용합니다.

 

첫 번째는 약한 참조(Weak Reference)입니다.

 

약한 참조는 ARC의 참조 횟수를 증가시키지 않으며,

 

참조 대상 객체가 해제되면 자동으로 nil로 설정됩니다.

 

일반적으로 순환 참조를 방지하기 위해 두 객체 중 한쪽을 weak로 선언합니다.

class Dog {
    var name: String
    var cat: Cat?
    
    init(name: String) {
        self.name = name
    }
}

class Cat {
	var legs: Int
    weak var dog: Dog?
    
    init(legs: Int) {
    	self.legs = legs
    }
}

var dog: Dog?
var cat: Cat?

dog = Dog(name: "Choco")
cat = Cat(legs: 4)

dog!.cat = cat
cat!.dog = dog

dog = nil
cat = nil

 

Cat의 dog 변수를 약한 참조로 선언하였습니다.

 

이 경우에는 dog를 약한 참조로 선언하였으므로 

 

ARC의 참조 횟수를 증가시키지않아,

 

참조 횟수가 0이 되면서 ARC는 객체를 제거할 수 있게 됩니다.

 

약한 참조를 사용할 때에는

 

상수가 아닌 변수로 선언을 해야하며, 옵셔널 타입이여야합니다.

 

참조하는 다른 인스턴스의 수명이 더 짧을 때(즉, 해당 인스턴스가 먼저 메모리에서 해제될 가능성이 있을 때) 약한 참조를 사용합니다.

 

약한 참조를 nil로 설정할 때에는 속성 관찰자(Property Observer)가 호출되지 않습니다.

 

 

6. 미소유 참조(Unkowned References)

또 한가지 방법은 미소유 참조(Unkowned References)를 사용하는 방법입니다.

 

미소유 참조는 약한 참조와 마찬가지로 강한 참조를 사용하는 것이 아니기 때문에 

 

ARC의 참조 횟수를 증가 시키지 않지만 약한 참조와 차이점이 존재합니다.

 

미소유 참조는 약한 참조와 다르게

 

nil 값으로 바뀌지 않습니다. 

 

따라서 옵셔널 타입으로 선언하지 않습니다.

 

또한 미소유 참조는 참조하는 인스턴스가 나 자신보다 오래 존재하거나 동일하게 존재할 때 사용합니다.

 

이번에도 코드를 보겠습니다.

 

class Dog {
    var name: String
    var cat: Cat?   // Dog가 Cat을 소유 (strong)
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Dog deinit")
    }
}

class Cat {
    var legs: Int
    unowned var dog: Dog   // Dog를 미소유 참조
    
    init(legs: Int, dog: Dog) {
        self.legs = legs
        self.dog = dog
    }
    
    deinit {
        print("Cat deinit")
    }
}

var dog: Dog? = Dog(name: "Choco")
dog?.cat = Cat(legs: 4, dog: dog!)

dog = nil

 

Dog가 Cat소유하고, Cat은 반드시 어떤 Dog에 속해 있다고 가정할 때,

 

Dog 객체가 생성되면서 참조 횟수가 1로 증가하지만

 

Cat은 Dog를 미소유 참조로 하기 때문에 참조 횟수가 증가하지 않아

 

ARC는 Dog를 해제할 수 있습니다.

 

이때 약한 참조는 nil 값으로 설정하기에 해제 후에도 접근할 수 있지만

 

무소유 참조는 접근시 에러가 발생합니다.

'Swift' 카테고리의 다른 글

Swift - 클래스(Class)  (0) 2026.04.15
Swift - 1급 객체(First-Class Object)  (0) 2026.04.03
Swift - 옵셔널(Optional)  (0) 2026.03.27
Swift -Async Await  (0) 2026.03.26
Swift - Generic  (0) 2026.02.27