---
id: ios-swift-memory-arc-cycles
title: Swift ARC — 강한 참조 사이클 회피
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [ios, swift, memory, arc, retain-cycle, vibe-coding]
tech_stack: { language: "Swift 5+", applicable_to: ["iOS", "macOS"] }
applied_in: []
aliases: [retain cycle, weak, unowned, capture list]
---

# Swift ARC — 강한 참조 사이클

> ARC 가 자동 해제하지만 **사이클은 자동 못 끊는다**. 클로저, delegate, 부모-자식 관계가 사이클의 단골. `[weak self]` 가 정답인 경우가 90%.

## 📖 핵심 개념
- ARC: 강한 참조 카운트 0 → deallocate.
- Strong: 기본. 카운트 +1.
- Weak: 카운트 안 셈. 대상 해제되면 자동 nil. Optional 만 가능.
- Unowned: 카운트 안 셈. 대상 해제되면 dangling — 접근 시 crash. Non-optional.

## 💻 코드 패턴

### 클로저 capture list
```swift
class ViewController: UIViewController {
    var dataSource: DataSource!

    override func viewDidLoad() {
        // ❌ self 강한 참조 → ViewController 가 dataSource 보유, dataSource 의 onUpdate 가 self 보유 → cycle
        dataSource.onUpdate = { items in
            self.tableView.reloadData()
        }

        // ✅ weak
        dataSource.onUpdate = { [weak self] items in
            self?.tableView.reloadData()
        }
    }
}
```

### 다중 capture
```swift
[weak self, weak viewModel] in
    guard let self, let viewModel else { return }
    // ...
}
```

### Delegate 패턴 — weak
```swift
protocol MyDelegate: AnyObject { ... } // class-only

class Sender {
    weak var delegate: MyDelegate? // 강한 참조 X
}
```

### Closure 가 self 의 자식인 경우
```swift
class Player {
    let video: VideoView
    private var onPlayCallback: (() -> Void)?

    init(...) {
        self.video = VideoView()
        // self 안의 video, video 의 callback 이 self 호출 → cycle
        self.onPlayCallback = { [weak self] in self?.didPlay() }
    }
}
```

### unowned — 절대 nil 안 되는 경우만
```swift
class Customer {
    let card: CreditCard
    init() { self.card = CreditCard(owner: self) }
}
class CreditCard {
    unowned let owner: Customer // CreditCard 는 항상 owner 보다 먼저 해제됨이 보장
    init(owner: Customer) { self.owner = owner }
}
```

## 🤔 의사결정 기준
| 관계 | 권장 |
|---|---|
| Closure 안의 self | `[weak self]` |
| Delegate | `weak` |
| 자식이 부모 참조 (반드시 부모 살아있음 보장) | `unowned` |
| Optional 가능 + 안전 | `weak` |
| KVO observer | weak (NSObject) |
| Combine subscription | sink 안에서 weak self |
| Swift Concurrency Task closure | `[weak self]` 또는 ViewModel 패턴 |

## ❌ 안티패턴
- **모든 곳에 unowned**: dangling 사고. 확신 없으면 weak.
- **`[unowned self]` 후 nil 접근**: crash. 절대 부모 먼저 해제 안 됨이 진실인지 검증.
- **`weak self` 후 force unwrap (`self!`)**: nil 일 때 crash. `guard let self else return`.
- **delegate 를 strong 으로 선언**: cycle. weak 표준.
- **NotificationCenter observer 해제 안 함**: leak. deinit 또는 자동 해제 (block 기반 일부).
- **Timer.scheduledTimer 가 self 강한 참조**: cycle. invalidate() 또는 weak self.
- **Combine cancellable 보관 안 함**: 즉시 해제. `var cancellables = Set<AnyCancellable>()`.

## 🤖 LLM 활용 힌트
- "closure capture 마다 self 사이클 점검. 90%는 [weak self]" 강조.
- Instruments Leaks / Memory Graph 로 검증.

## 🔗 관련 문서
- [[iOS_Swift_Concurrency_async_await]]
- [[iOS_Network_URLSession_Patterns]]
