Unowned Reference(비소유 참조)는 Swift에서 객체의 retain count를 증가시키지 않고 weak와 달리 객체 해제 후 nil로 설정되지 않는 비소유 참조입니다. Apple Swift Language Guide, 2026에 따르면, unowned는 객체가 이를 참조하는 객체만큼 최소한 동일한 기간 동안 살아있음이 보장될 때 사용됩니다. Weak Reference와 달리 unowned는 언래핑이 필요하지 않습니다. 이는非옵셔널 타입으로 코드를 더 깔끔하게 만들지만 생애 주기 보장에 대한 책임을 개발자에게 부여합니다.
핵심 포인트
Unowned Reference는 ARC에서 객체에 대한 비소유 참조로, retain count를 증가시키지 않습니다. weak와 달리 unowned 참조는 객체 해제 후 제로잉되지 않습니다. 이미 해제된 메모리를 계속 가리킵니다. 이러한 참조에 접근하면 EXC_BAD_ACCESS와 함께 런타임 충돌이 발생합니다.
“비소유”라는 용어는 의미를 반영합니다. 객체는 존재하지만 그 생애 주기에 대해 책임지는 사람이 없습니다. 개발자는 명시적으로 선언합니다: “제가 참조하는 한 이 객체가 살아있을 것을 보장합니다.” 컴파일러는 이 보장을 확인하지 않습니다 — 이는 개발자 수준의 계약입니다.
Swift.org Documentation, 2026에 따르면, 생애 주기가 보장된 시나리오에서는 unowned 참조가 weak보다 선호됩니다. 그 이유는 옵셔널 타입이 필요하지 않고(더 깔끔한 코드), 언래핑이 필요하지 않으며(force-unwrap 또는 guard let 감소), 제로잉을 위한 weak 테이블 유지 오버헤드가 없기 때문입니다. 그러나 계약 위반은 충돌로 이어집니다.
Swift에서 unowned 참조는 let 또는 var 앞에 키워드 unowned를 붙여 선언합니다. weak와 달리 unowned는 let과 var 모두 가능하며 옵셔널 타입이 필요하지 않습니다. 이 특성으로 인해 unowned는 도메인 로직상 nil이 될 수 없는 참조에 편리합니다.
class Country {
let name: String
var capital: City! // 초기화 후 설정됩니다
init(name: String) { self.name = name }
}
class City {
let name: String
unowned let country: Country // ✅ unowned let — 생애 주기 보장
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
// 사용법
let france = Country(name: "France")
let paris = City(name: "Paris", country: france)
france.capital = paris
// ✅ Country → City (strong), City → Country (unowned) — retain cycle 없음
이 예시에서 City unowned let country — 도시는 국가 없이 존재할 수 없습니다. 국가가 사라지면 도시(와 참조)는 의미를 잃습니다. 의미론적으로 이는 unowned에 이상적인 경우입니다. 생애 주기 보장이 존재하고, 옵셔널이 필요하지 않으며, retain cycle이 발생하지 않습니다.
unowned var는 허용되지만 덜 일반적입니다. 참조를 바꿀 수 있는 경우(예: 자식을 다른 부모에 다시 바인딩)에 사용됩니다. 재할당 시 이전 객체의 해제는 외부 소유자의 책임입니다.
Swift 5.0+에서 unowned 옵셔널(unowned let x: Type?) 지원이 도입되었습니다. 이는 절충안입니다. unowned는 참조가 nil이 아니면 객체가 살아있음을 보장합니다. 해제 시 동작은 일반 unowned와 마찬가지로 충돌입니다.
unowned와 weak 중 선택은 Swift 아키텍처를 설계할 때 자주 마주치는 결정 중 하나입니다. 각 경우의 기준과 권장 사항을 살펴보겠습니다.
| 기준 | Weak | Unowned |
|---|---|---|
| 옵셔널 | 예 (Type?) | 아니오 (Type) |
| 해제 시 제로잉 | 자동으로 nil | 아니오(댕글링 포인터 위험) |
| 타입 (let/var) | var만 | let 또는 var |
| 성능 | weak 테이블 오버헤드 | 최소(단순 포인터) |
| 안전성 | 안전(nil 확인됨) | EXC_BAD_ACCESS 위험 |
| 생애 주기 보장 | 필요 없음 | 명시적 보장 필요 |
weak 사용은 객체의 생애 주기에 대해 조금이라도 의심이 있을 때입니다. Weak는 안전하고 명확하며 증명이 필요하지 않습니다. unowned 사용은 객체가 더 일찍 해제될 수 있는 모든 시나리오를 배제할 수 있을 때만 가능합니다. 일반적인 경우: 부모 없이 존재할 수 없는 자식, 동기적으로 실행되는 클로저, 초기화자 내에서 객체에 접근.
Airbnb Swift Style Guide, 2025에 따르면, 대규모 코드베이스에서는 기본적으로 weak를 사용하고 unowned는 생애 주기 보장을 설명하는 명시적 주석과 함께만 사용하는 것이 권장됩니다. 이는 리팩토링 중 명확하지 않은 충돌 위험을 줄여줍니다.
클로저는 부모-자식 관계 다음으로 unowned의 두 번째로 빈번한 사용 사례입니다. 캡처 목록 [unowned self]는 self가 클로저보다 오래 살 것이 보장될 때 사용됩니다. 올바른 시나리오와 잘못된 시나리오를 살펴보겠습니다.
동기 클로저 — sorted, filter, map. 현재 스레드에서 즉시 실행되며 self는 확실히 살아있습니다. unowned를 사용한 캡처 목록은 여기서 허용되며 더 깔끔한 코드를 제공합니다.
class DataProcessor {
var items: [Int] = [3, 1, 4, 1, 5]
func processSorted() {
// ✅ unowned self — sorted가 동기적으로 실행, self가 보장된 생존
let sorted = items.sorted { [unowned self] a, b in
return self.customCompare(a, b)
}
}
func customCompare(_ a: Int, _ b: Int) -> Bool { return a < b }
}
비동기 클로저 — 지연, 네트워크 요청, 애니메이션이 있는 경우. 클로저 예약과 실행 사이에 self가 해제될 수 있습니다. 이 경우 unowned self는 충돌로 이어집니다. [weak self]를 사용하세요.
class NetworkLoader {
func loadData() {
// ❌ 위험: 비동기 클로저에서 unowned self
URLSession.shared.dataTask(with: url) { [unowned self] data, _, _ in
self.handleResponse(data) // self 해제 시 충돌
}.resume()
}
func handleResponse(_ data: Data?) { }
// ✅ 정답: weak self + guard
func loadDataSafe() {
URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
guard let self else { return }
self.handleResponse(data)
}.resume()
}
}
규칙을 기억하세요: unowned self — 즉시 실행되는 동기 클로저만 해당. 비동기 클로저의 경우 항상 weak self + guard let을 사용하세요. 예외: 클로저가 완료될 때까지 객체에 대한 참조를 명시적으로 유지하는 경우(예: 다른 변수에 강한 참조 유지).
Unowned는 강력하지만 위험한 도구입니다. unowned가 충돌로 이어질 수 있는 실제 시나리오와 위험 최소화 방법을 살펴보겠습니다.
unowned의 주요 위험은 생애 주기 보장을 무효화하는 비즈니스 로직 변경입니다. 개발자가 코드를 리팩토링하여 소유권을 변경하거나, 지연 해제를 도입하거나, 캐싱을 추가하면 unowned 참조가 시한폭탄이 됩니다. 컴파일러는 경고하지 않습니다 — 사용자 기기에서 충돌만 발생합니다.
권장 사항: 생애 주기 보장이 명백하고 문서화된 경우에만 unowned를 사용하세요. 각 unowned에 주석을 추가하세요: 이 참조가 안전한 이유와 어떤 조건에서 위반될 수 있는지.
UIKit은 unowned에 고위험 영역입니다. ViewController는 탐색(pop, dismiss), 메모리 언로드, 방향 변경 중 언제든지 해제될 수 있습니다. unowned self와 함께 ViewController를 클로저에 전달하면 백그라운드에서 돌아오거나 애니메이션 완료 시 self가 nil일 수 있습니다.
unowned 사용 시 위험을 줄이려면 다음 규칙을 따르세요:
// 예: 명시적 정당성과 함께 문서화된 unowned 참조
class InvoiceLineItem {
let productName: String
let price: Decimal
// unowned Invoice — InvoiceLineItem은 Invoice 없이 존재할 수 없습니다.
// Invoice는 Item을 생성하고 삭제 시 제거합니다.
// 보장: Invoice는 Item과 최소한 동일한 기간 동안 살아있습니다.
unowned let invoice: Invoice
init(productName: String, price: Decimal, invoice: Invoice) {
self.productName = productName
self.price = price
self.invoice = invoice
}
}
// 이것은 강력한 보장입니다: Invoice는 deinit에서 모든 Item을 제거합니다.
// 보장 위반 = 수정이 필요한 비즈니스 로직 버그.
보장 문서화는 전문가 표준입니다. 대규모 프로젝트(Airbnb, Uber)에서는 코드 리뷰에서 각 unowned에 대한 정당성이 요구됩니다. 보장이 명확하지 않으면 weak를 사용하세요. unowned에 대한 주석은 미래의 개발자가 왜 여기서 weak를 사용하지 않았는지, 어떤 조건이 보장을 깨뜨릴 수 있는지 이해하는 데 도움이 됩니다.
자주 묻는 질문
런타임 충돌(EXC_BAD_ACCESS). Swift는 접근 시 unowned 참조의 유효성을 확인하지 않습니다 — 단순한 “원시” 포인터입니다. 객체가 해제되면 메모리가 덮어쓰여지고 접근이 치명적으로 종료됩니다. 이는 잡을 수 없는 예외입니다(try-catch 불가).
네, 프로토콜이 AnyObject를 상속한 경우 가능합니다. Unowned는 모든 참조 타입(클래스, AnyObject 프로토콜, Objective-C 객체)에서 작동합니다. 값 타입(struct, enum)은 ARC에 참여하지 않으므로 unowned를 지원하지 않습니다.
생애 주기 보장이 절대적이고 명백할 때 — unowned는 설계 관점에서 더 안전합니다. 언래핑이 필요 없고, nil이 될 수 없으며, 오류를 숨기지 않습니다. 객체가 부모 없이 존재할 수 없는 경우 unowned는 이를 명시적 계약으로 만드는 반면, weak는 보장을 흐릿하게 만듭니다.
네: unowned가 더 빠릅니다. 제로잉을 위해 런타임에 weak 테이블에 접근할 필요가 없기 때문입니다. 대부분의 애플리케이션에서 차이는 느껴지지 않지만, 수백만 회 접근이 있는 고부하 시나리오에서는 unowned가 읽기에서 10–20% 더 빠를 수 있습니다.
리팩토링은 unowned의 주요 위험입니다. 객체의 생애 주기 변경(캐싱, 비동기 작업, 재사용)은 보장을 깨뜨릴 수 있습니다. 컴파일러가 경고하지 않습니다. 해결책: 아키텍처 변경 시 weak로 전환하거나 경고 주석을 추가하세요.
요약
unowned let 또는 unowned var; 비옵셔널 및 옵셔널(Swift 5.0+) 가능턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.