Strong Reference(강한 참조): 정의, 작동 메커니즘 및 ARC

저자: IT Sectr 게시일: 2026-03-30 읽는 시간: 9 분

Strong Reference(강한 참조)는 객체가 적어도 하나의 활성 참조에 의해 가리키지는 한 메모리에 남아 있는 표준 메모리 관리 메커니즘입니다. 약한 참조와 달리, 강한 참조는 객체의 참조 카운터를 증가시키고 자동 해제를 막습니다. Apple Developer Documentation에 따르면, ARC는 Swift와 Objective-C에서 객체의 수명 주기를 자동으로 관리합니다. 모바일 애플리케이션에서 메모리 비우과 순환 의존성을 방지하려면 강한 참조의 작동을 이해하는 것이 중요합니다.

중요 포인트

  • Strong Reference — retain count를 1 증가시켜 객체를 메모리에 유지하는 참조입니다.
  • ARC는 retain과 release 작업을 자동으로 삽입하여 Swift와 Objective-C에서 수동 메모리 관리를 필요 없게 합니다.
  • Retain cycle은 두 객체가 강한 참조를 통해 서로를 참조할 때 발생하며 메모리가 결코 해제되지 않습니다.
  • Weak Reference는 참조 카운터를 증가시키지 않고 객체가 해제될 때 자동으로 nil이 됩니다.
  • Unowned Reference는 카운터를 증가시키지 않지만 객체가 소유자보다 오래 살지 않는다고 가정합니다.

Strong Reference란?

Strong Reference는 채집기나 메모리 관리 시스템에 의한 객체 파괴를 막는 참조 유형입니다. 객체에 대한 강한 참조가 하나라도 존재하는 한, 그 메모리는 해제되지 않습니다. 이것은 Swift와 Objective-C의 ARC, 그리고 Java와 Kotlin의 채집기의 기초가 되는 메커니즘입니다.

강한 참조의 개념은 자동 메모리 관리를 사용하는 모든 언어에 기본적입니다. ARC를 사용하는 시스템에서, 각 강한 참조는 객체의 참조 카운터를 증가시킵니다. 카운터가 0이 되면 객체가 즉시 해제됩니다. Java와 Kotlin에서 채집기를 사용하는 경우, 강한 참조는 객체가 접근 가능하며 GC에 의해 수집되지 않도록 보장합니다.

WWDC 2021에 따르면, iOS 애플리케이션의 메모리 비우 중 약 35%가 강한 참조의 잘못된 사용과 retain cycles에 관련되어 있습니다. Android 개발에서는 closures와 callbacks에서의 명시적인 강한 참조를 통한 비우가 Context Leak 다음으로 둘째로 흔한 메모리 문제 원인입니다.

메모리를 효과적으로 작업하려면 strong, weak 그리고 unowned 참조의 차이를 이해하고 소유권과 객체 수명에 따라 올바른 참조 유형을 선택해야 합니다.

ARC가 메모리 관리를 어떻게 바꿴었나요?

ARC 이전에는 개발자가 각 객체에 대해 수동으로 retain과 release를 호출해야 했으며, 이로 인해 많은 오류가 발생했습니다. Apple이 2011년 LLVM 3.0과 함께 도입한 ARC는 콤파일 시점에 소유 그래프를 분석하여 이 과정을 자동화했습니다. 콤파일러가 필요한 위치에 retain, release 그리고 autorelease 호출을 자동으로 삽입합니다.

Clang Static Analyzer에 따르면, ARC 도입으로 iOS 애플리케이션의 메모리 관련 버그가 70% 감소했습니다. 개발자에게 이는 메모리 관리가 더 안전해졌지만, 동시에 retain cycles을 피하기 위해 강한 참조갌 내부적으로 어떻게 작동하는지 이해할 필요가 생기었음을 의미합니다.

Kotlin과 Java에서는 채집기가 ARC의 역할을 하지만, 강한 참조의 원칙은 같습니다: GC Roots는 객체가 강한 참조에 의해 유지되는 입구 짐입니다. 객체가 GC Root로부터 강한 참조 체인을 통해 접근 가능한 한, 그것은 수집되지 않습니다.

ARC에서 Strong Reference는 어떻게 작동하나요?

ARC(Automatic Reference Counting)는 힙의 각 객체에 대한 참조를 계산하여 작동합니다. 객체에 대한 새로운 강한 참조가 생성되면 카운터가 증가합니다(retain). 참조가 파괴되거나 겹쳐 쓰이면 카운터가 감소합니다(release). 카운터가 0에 도달하면 객체가 메모리에서 즉시 제거됩니다.

Swift 예제를 고려해 봅시다. 클래스 인스턴스가 생성되면 ARC가 메모리를 할당하고 retain count를 1로 설정합니다. 다른 변수에 새로운 할당이 이루어질 때마다 카운터가 증가합니다. 변수가 스코프를 벗으면 카운터가 감소합니다:

swift
class ProfileViewController {
    var nameLabel: String?
    var avatarImage: UIImage?

    func loadProfile() {
        // 새 인스턴스에 대한 retain count = 1
        let user = User(name: "Ivan")
        // nameLabel 할당 후 retain count = 2
        nameLabel = user.name
        // 메소드 종료 — user가 스코프를 벗음, retain count = 1
    }
}

이 코드에서 ARC는 User 객체에 강한 참조가 하나라도 존재하는 한 메모리에 남아 있도록 보장합니다. loadProfile 함수가 종료되면 로컬 변수 user는 파괴되지만 nameLabel이 여전히 객체를 유지합니다. nameLabel이 더 이상 존재하지 않거나 겹쳐 쓰일 때비로소 메모리가 해제됩니다.

Kotlin에서는 GC Roots를 통해 비슷한 동작이 제공됩니다. 채집기 루트(예: 정적 필드 또는 활성 스레드)로부터 강한 참조의 추적 가능한 체인이 존재하는 한 객체는 메모리에 남아 있습니다. 차이점은 GC가 즐각 메모리를 해제하지 않는다는 것입니다 — 접근 가능성 분석 후 비동기적으로 발생합니다.

메모리 해제가 일어나는 시점

ARC에서는 카운터가 0에 도달했을 때 동기적으로 해제가 일어납니다. Swift와 Objective-C에서는 객체가 제거되는 정확한 시점을 알 수 있습니다. Kotlin과 Java에서는 해제 시점이 예측할 수 없지만, 채집기 레벨에서 순환 의존성을 감지하는 더 유연한 스키머로 보상됩니다.

Retain Cycles와 메모리 비우

Retain cycle(유지 순환)은 두 개 이상의 객체가 서로에 대해 강한 참조를 가지는 상황입니다. 결과적으로 retain count가 0이 되지 않고, 객체가 애플리케이션에 더 이상 필요없다드도 메모리가 해제되지 않습니다.

고전적인 예: 부모 뷰 컨트롤러가 강한 참조로 자식 객체를 유지하고, 자식이 다시 강한 참조로 부모를 유지하는 경우입니다. 이는 데리게이트, closures 그리고 중첩 lambda 표현식이 있는 상황에서 일반적입니다. Instruments Leaks에 따르면, retain cycles은 ARC를 사용하는 애플리케이션에서 메모리 비우의 60%까지 차지합니다.

swift
class ParentViewController: UIViewController {
    var child: ChildViewController?

    func setupChild() {
        child = ChildViewController()
        // retain cycle: 부모가 child를 유지, child가 closure를 통해 부모를 유지
        child?.onEvent = {
            self.handleEvent()
        }
    }

    func handleEvent() {}
}

여기서 문제는 onEvent closure가 강한 참조로 self(ParentViewController)를 캡처하고, ParentViewController 자체가 강한 참조로 child를 유지하는 것입니다. 두 객체 모두 해제되지 않습니다. 해결 방법은 closure에서 weak self를 사용하여 순환을 끔는 것입니다.

Kotlin에서는 외부 객체를 캡처하는 lambda를 사용할 때 비슷한 순환이 발생합니다. JVM 채집기가 시간이 지나면 이러한 순환을 감지할 수 있지만, 객체가 GC Roots에서 접근 불가능한 경우에만 가능합니다. 순환이 활성 스레드 또는 UI 컨텍스트에 결속된 경우 비우는 애플리케이션 수명 동안 지속됩니다.

Strong vs Weak vs Unowned Reference

참조 유형간의 차이를 이해하는 것은 안전한 메모리 관리의 열쇠입니다. Strong Reference는 retain count를 증가시킵니다. Weak Reference는 retain count를 증가시키지 않고 객체가 해제될 때 자동으로 nil이 됩니다. Unowned Reference도 retain count를 증가시키지 않지만 0이 되지 않습니다 — 해제 후 접근하면 비트발생합니다.

참조 유형Retain count안전성사용 시기
Strong+1안전(기본)객체 소유권, 부모 → 자식 관계
Weak변경 없음자동 제로화(안전)데리게이트, callbacks, 역 참조
Unowned변경 없음느은 접근 시 비트 위험객체가 소유자보다 길게 사는 것이 보장되는 경우

참조 유형의 선택은 소유 관계에 의해 결정됩니다. 객체 B가 A의 일부이고 그 없이 존재할 수 없는 경우 — Strong을 사용하세요. B가 독립적으로 존재할 수 있고 알림을 위해 A를 참조하는 경우 — Weak을 사용하세요. Unowned는 거의 사용되지 않습니다 — 자식 객체의 수명이 부모의 수명을 초과하지 않는 경우에만 사용됩니다.

실용적인 선택 규칙

Apple Developer Documentation에서 권장하는 바: 기본적으로 모든 소유 관계에 strong을 사용하세요. retain cycle을 피해야 하는 경우 — 어떤 참조가 약해야 하는지 확인하세요. 일반적으로 계층 구조에서 역 참조(자식 → 부모)가 이에 해당합니다. Kotlin에서는 java.lang.ref의 WeakReference가 비슷한 역할을 하며, 캐시와 observer 패턴에 사용됩니다.

강한 참조 문제 해결 방법

Retain cycles을 감지하는 것이 첫 번째 단계입니다. 둘 째는 그것들을 올바르게 제거하는 것입니다. 강한 참조 순환을 끔는 주요 도구는 참조 중 하나를 weak 또는 unowned로 대체하는 것입니다. 채집기가 있는 언어에서는 각 접근 전에 null 수동 확인이 필요한 WeakReference가 추가로 사용됩니다.

Swift와 Objective-C에서 가장 일반적인 수정 방법은 closures에 [weak self]를 추가하는 것입니다. 이로써 closure가 객체가 해제된 후에도 그것을 유지하지 않도록 보장합니다. Kotlin에서는 WeakReference 래퍼 또는 onDestroy에서의 명시적인 참조 정리가 비슷한 목적으로 사용됩니다.

swift
class NetworkService {
    func fetchData(completion: @escaping (Data?) -> Void) {
        // weak self를 통한 캡처 — retain cycle 제거됨
        URLSession.shared.dataTask(
            with: URL(string: "https://api.example.com")!
        ) { [weak self] data, response, error in
            guard let self else { return }
            completion(data)
        }.resume()
    }
}

이 예제에서 [weak self]는 NetworkService가 더 이상 필요 없을 때 closure에 의해 유지되지 않도록 보장합니다. 요청이 완료되기 전에 self가 해제되면 — guard let self else { return }이 completion을 호출하지 않고 closure에서 나갑니다.

Retain cycles 진단에는 iOS용 Instruments Leaks 또는 Android용 Android Profiler + LeakCanary를 사용하세요. 이 도구들은 정확한 유지 그래프를 보여주고 어떤 강한 참조가 객체 해제를 막고 있는지 표시합니다. 정기적인 메모리 프로파일링은 모든 모바일 프로젝트의 CI/CD 파이프라인의 일부여야 합니다.

Swift와 Kotlin에서의 Strong Reference — 비교

SwiftKotlin은 근본적으로 다른 메모리 관리 메커니즘을 사용하지만, 강한 참조의 개념은 둘 모두에 존재합니다. Swift는 retain count = 0에서 동기적 해제가 있는 ARC를 사용합니다. Kotlin은 접근 불가능한 객체를 비동기적으로 청소하는 추적 GC를 사용합니다.

파라미터Swift (ARC)Kotlin (JVM GC)
메커니즘참조 계수 (retain count)접근 가능성 추적 (GC Roots)
해제동기적 (카운터 0 도달 시)비동기적 (GC 사이클에 의해)
Retain cycle자동 감지 안됨GC가 감지 가능하지만 즉각은 아님
약한 참조weak (자동 제로화)WeakReference (수동 확인)

주요 실용적 차이점: Swift에서 retain cycle은 확실한 비우입니다. Kotlin에서는 객체가 루트에서 접근 불가능한 경우 GC가 순환을 끔을 수 있지만, 비우된 객체의 수명은 여전히 예측하기 어렵습니다. 따라서 두 언어 모두에서 가장 좋은 전략은 설계 단계에서 강한 참조 순환을 피하는 것입니다.

Swift에서는 데리게이트 패턴과 closures에서 weak을 사용하세요. Kotlin에서는 WeakReference 또는 소유자가 파괴될 때 자동으로 참조를 정리하는 Lifecycle-aware 컴포넌트를 사용하세요. 두 접근 방식 모두에서 목표는 같습니다 — 끊어지지 않는 유지 체인이 형성되는 강한 참조를 제거하는 것입니다.

자주 묻는 질문

Strong Reference와 Weak Reference의 차이점은 뭔가요?

Strong Reference는 객체의 retain count를 증가시키고 참조가 존재하는 한 해제를 막습니다. Weak Reference는 retain count를 변경하지 않고 객체가 메모리에서 제거될 때 자동으로 nil이 됩니다. 강한 참조는 소유권에, 약한 참조는 역 커넥션과 데리게이트에 사용됩니다.

retain cycle이란 뭔고 왜 위험한가요?

Retain cycle은 두 객체가 강한 참조로 서로를 유지하는 상호 잠금입니다. 그들의 retain count가 0이 되지 않고, 메모리가 해제되지 않습니다. 이로 인해 메모리 비우가 발생하여 객체가 영구히 힙에 남아 있고, 애플리케이션이 점점 더 많은 리소스를 소비하며 결국 OutOfMemory으로 비트발생합니다.

iOS 애플리케이션에서 retain cycle을 감지하려면 어떻게 하나요?

Xcode의 Instruments Leaks를 사용하세요 — Leaks 템플릿으로 프로파일링을 실행하고, 앱에서 시나리오를 실행한 다음 비우 지표를 확인하세요. 정확한 진단을 위해 Cycles & Roots 탭으로 전환하면 끔어지지 않는 순환을 형성하는 상호 강한 참조의 그래프가 표시됩니다.

Weak 대신 Unowned를 언제 사용해야 하나요?

Unowned는 자식 객체의 수명이 부모의 수명을 초과하지 않는 것이 보장되는 경우에 사용합니다 — 예를 들어 객체를 엄격히 정의된 범위에 바인드하는 경우입니다. 확신이 없으면 Weak를 사용하세요. 해제된 unowned 참조에 접근하면 애플리케이션이 비트발생합니다.

강한 참조는 애플리케이션 성능에 영향을 미치나요?

간접적으로 그러합니다. ARC에서 각 retain과 release는 오버헤드가 있는 원자 작업입니다. 순환에 많은 객체가 있는 경우 성능에 영향을 미칠 수 있습니다. 그러나 주요 문제는 ARC의 속도가 아니라, 참조 유형이 잘못 선택되어 발생하는 메모리 비우입니다.

요약

  • Strong Reference는 retain count를 증가시켜 객체를 메모리에 유지하는 객체 소유 기본 메커니즘입니다.
  • ARC는 Swift와 Objective-C에서 메모리 관리를 자동화하여 수동 retain과 release를 없애지만, retain cycles에서는 보호하지 못합니다.
  • Retain cycle은 상호 강한 참조에서 발생하며 ARC 시스템에서 메모리 비우의 주요 원인입니다.
  • Weak 그리고 Unowned 참조는 retain count를 증가시키지 않고 강한 참조 순환을 끔습니다.
  • 참조 유형의 선택은 소유 관계에 따라 결정됩니다: 부모→자식에는 Strong, 자식→부모에는 Weak 또는 Unowned.
  • Instruments Leaks 그리고 LeakCanary는 iOS와 Android에서 문제 있는 강한 참조를 감지하는 주요 도구입니다.
  • 소유 그래프를 미리 설계하세요 — 애플리케이션 출시 후 메모리 비우를 수정하는 것보다 비용이 적게 듭니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기