본문 바로가기
TIL(Today I Learned)

Swift의 ARC

by 승환파크 2025. 3. 1.

Swift는 메모리 관리를 자동으로 처리하기 위해서 ARC(Automatic Reference Counting)를 사용한다. ARC는 객체가 사용되는 동안 참조 카운트(Reference Counting)를 관리하여, 더 이상 필요하지 않으면 객체를 자동으로 메모리에서 해제한다.

하지만 객체 간 강한참조(Strong Reference)를 잘못 사용하면, 순환 참조(Strong Reference Cycle)가 발생하여 메모리에서 해제되지 않는 문제가 발생할 수 있다.

ARC(Automatic Reference Counting)의 동작 원리

ARC는 객체가 할당되면 참조 카운트를 증가시키고, 더 이상 필요하지 않을 때 참조 카운트가 0이 되면 메모리에서 해제된다.

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
        print("\(name) 객체 생성")
    }
    
    deinit {
        print("\(name)" 객체 해제")
    }
}

var person1: Person? = Person(name: "jon") // 참조 카운트 1
person1 = nil // 참조 카운트 0 -> 객체가 메모리에서 해제됨

// 결과
// jon 객체 생성
// jon 객체 해제

 

Strong, Weak, Unowned 참조

객체 간의 참조 방식에 따라 메모리 관리 방식이 달라질 수 있다.

Swift 에서는 Strong(강한 참조), Weak(약한 참조), Unowned(미소유 참조) 이렇게 세 가지 참조 방식을 지원한다.

1. Strong(강한 참조)

모든 변수와 프로퍼티는 기본적으로 strong 참조를 한다. 하지만 객체가 서로 강한 참조를 가지면 순환 참조(Strong Reference Cycle)가 발생하여 메모리가 해제되지 않을 수 있다.

class Person {
    let name: String
    var pet: Pet?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) 해제됨")
    }
}

class Pet {
    let name: String
    var owner: Person?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) 해제됨")
    }
}

var person: Person? = Person(name: "jon")
var pet: Pet? = Pet(name: "coco")

person?.pet = pet
pet?.owner = person // 순환 참조 발생

person = nil
pet = nil // 메모리에서 해제되지 않음 (메모리 누수 발생)

 

이 코드는 강한 참조로 인한 순환 참조가 되는 상황을 보여주는 예시 코드이다. 만약 이 코드에서 순환 참조가 되지 않게 하려면 weak 나 unowned 키워드를 사용해야 한다.

2. Weak(약한 참조)

weak 키워드를 사용하면 참조 카운트가 증가하지 않는다. 객체가 해제되면 weak 참조는 자동으로 nil이 된다.

class Pet {
    let name: String
    weak var owner: Person? // 약한 참조

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) 해제됨")
    }
}

Strong 예시 코드에서 weak를 사용한 순환 참조 해결 예시 코드이다. weak를 사용하여 Person 이 nil이 되면 Pet의 owner가 자동으로 nil이 되어 메모리에서 정상적으로 해제가 된다.

3. Unowned(미소유 참조)

unowned는 weak 와 비슷하지만, nil을 허용하지 않는다는 차이점이 존재한다. 즉, 객체가 해제되었는데 unowned 참조를 사용하면 런타임 에러가 발생할 수 있다.

class CreditCard {
    let number: String
    unowned let owner: Person

    init(number: String, owner: Person) {
        self.number = number
        self.owner = owner
    }

    deinit {
        print("카드 \(number) 해제됨")
    }
}

이 예시 코드를 봤을 때 unowned 를 사용하면 Person 객체가 메모리에서 해제될 때 CreditCard 에서의 Person 객체도 메모리에서 정상적으로 해제된다. 하지만, unowned 는 nil을 허용하지 않기 때문에 아직 .owner 는 메모리에 없는 person 을 참조하려 한다. 그렇기 때문에 person 이 메모리에서 해제가 되었을 때 접근을 하면 런타임 에러가 발생할 수 있다.

 

클로저에서의 ARC 관리

클로저는 기본적으로 캡쳐를 수행하여 클로저 내부에서 사용되는 객체의 참조 카운트를 증가시킨다. 이로 인해 클로저와 객체간의 순환 참조가 발생할 수 있다.

class Person {
    let name: String
    var sayHello: (() -> Void)?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("\(name) 해제됨")
    }
}

var person: Person? = Person(name: "jon")

person?.sayHello = {
    print("안녕하세요, 저는 \(person!.name)입니다!") // person을 강하게 참조하여 순환 참조 발생
}

person = nil // 메모리 해제되지 않음

위 예시코드를 보면 person?.sayHello 클로저 내부에서 person!.name을 사용하면서 self(person)을 강한 참조로 캡쳐하여 사용한다. 그렇기 때문에 person = nil을 해도 클로저 내부에서 person을 참조하므로 메모리 누수가 발생할 수 있다.

클로저에서의 순환 참조 방지(캡쳐 리스트 사용)

위의 예시 코드에서 순환 참조를 방지하기 위해서 클로저 내부에서 weak 또는 unowned를 사용하여 객체를 캡처할 때 참조 카운트를 증가시키지 않도록 해야한다.

weak 키워드 사용 예시

person?.sayHello = { [weak person] in
    print("안녕하세요, 저는 \(person?.name ?? "알 수 없음")입니다!")
}

weak 키워드를 사용하면 person이 해제될 때 자동으로 nil이 되어 순환 참조가 발생하지 않는다.

unowned 키워드 사용 예시

person?.sayHello = { [unowned person] in
    print("안녕하세요, 저는 \(person.name)입니다!")
}

unowned 키워드는 객체가 항상 존재한다고 가정할 때 사용한다. 하지만 person 객체가 먼저 해제되었는데 클로저가 호출하면 런타임 에러가 발생할 수 있다.

키워드 ARC 카운트 증가 nil 허용 여부 사용 시기
strong 증가 nil 불가능 일반적인 객체 참조
weak 증가 안함 nil 가능 API 호출, 비동기 작업, 객체가 사라질 수 있는 경우
unowned 증가 안함 nil 불가능 순환참조가 일어날 수 있고, 객체가 반드시 존재한다고 가정할 때 사용

 

'TIL(Today I Learned)' 카테고리의 다른 글

Swift 에러 처리  (2) 2025.03.14
2025.02.24 Today I Learned  (0) 2025.02.24
2025.02.14 Today I Learned  (0) 2025.02.14
2025.02.05 Today I Learned  (0) 2025.02.05
2025.01.24 Today I Learned  (0) 2025.01.24