이번 모의면접 질문에서 순환 참조를 어떻게 해결하는지에 대한 꼬리질문이 나왔는데, 분명 공부했음에도 답변을 하지 못했다. 그래서 다시 공부해야 할 필요성을 느끼고 순환 참조 해결에 대해 짚어봤다.
순환 참조(Circular Reference)
순환 참조(Circular Reference)란 두 개 이상의 객체가 서로를 참조하고 있어, 이 객체들이 메모리에서 해제되지 않고 계속 남아 있는 상황을 말한다. Swift에서는 ARC(Automatic Reference Counting)를 사용하여 메모리를 관리하며, 이 순환 참조를 적절히 해결하지 않으면 메모리 누수가 발생할 수 있는데, UIKit과 같은 프레임워크를 사용할 때 순환 참조는 주로 클로저와 델리게이트 패턴을 사용할 때 발생한다.
순환 참조 해결 방법
[ 클로저에서 약한 참조(Weak Reference)와 비소유 참조(Unowned Reference) 사용 ]
클로저 내에서 self를 캡처할 경우 순환 참조가 발생할 수 있는데, 이를 해결하기 위해 클로저의 캡처 목록에 약한 참조(Weak Reference)나 비소유 참조(Unowned Reference)를 사용하여 약한 참조 또는 비소유 참조로 만들어 방지할 수 있다.
약한 참조(weak) : 참조 대상이 nil이 될 수 있는 경우 사용하며, 참조 대상이 메모리에서 해제될 때 nil로 설정된다.
class DogDevClass {
var dogDevClosure: (() -> Void)?
func configureClosure() {
dogDevClosure = { [weak self] in
guard let self = self else { return }
self.doSomething()
}
}
func doSomething() {
print("공부해!!")
}
}
비소유 참조(Unowned) : 참조 대상이 항상 존재한다고 확신할 수 있을 때 사용하며, 대상이 메모리에서 해제되면 런타임 에러가 발생한다.
class DogDevClass {
var dogDevClosure: (() -> Void)?
func configureClosure() {
dogDevClosure = { [unowned self] in
self.doSomething()
}
}
func doSomething() {
print("걔팔자 공부 해라.")
}
}
[ 델리게이트(Delegate) 패턴에서 약한 참조(Weak Reference) 사용 ]
델리게이트 패턴 사용 시, 델리게이트를 약한 참조로 선언하여 순환 참조를 방지할 수 있다. 델리게이트는 주로 ViewController와 그 하위 View, Model 간의 통신에서 사용한다.
protocol dogDevDelegate: AnyObject {
func didPerformAction()
}
class DogDevClass {
weak var delegate: dogDevDelegate?
func performAction() {
delegate?.didPerformAction()
}
}
class ViewController: UIViewController, MyDelegate {
let dogDevClass = DogDevClass()
override func viewDidLoad() {
super.viewDidLoad()
dogDevClass.delegate = self
}
func didPerformAction() {
print("걔팔자야 공부해.")
}
}
[ 캡처 목록 사용 ]
클로저가 특정 객체를 캡처할 때 캡처 목록을 사용하여 특정 참조를 약한 참조로 만들 수 있다.
class DogDevClass {
var dogDevClosure: (() -> Void)?
func configureClosure() {
dogDevClosure = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething()
}
}
func doSomething() {
print("걔팔자는 코딩을 해요.")
}
}
[ NotificationCenter 사용 시 약한 참조 사용 ]
NotificationCenter을 사용할 때 순환 참조를 피하기 위해 약한 참조를 사용할 수 있으며, 클로저 기반의 옵저버를 사용하는 경우에도 약한 참조를 사용할 수 있다.
NotificationCenter을 사용
class DogDevClass {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), name: .someNotificationName, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc func handleNotification() {
// 알림 처리
}
}
extension Notification.Name {
static let someNotification = Notification.Name("someNotificationName")
}
클로저 기반의 옵저버를 사용
class DogDevClass {
init() {
NotificationCenter.default.addObserver(forName: .someNotificationName, object: nil, queue: .main) { [weak self] notification in
self?.handleNotification(notification)
}
}
@objc func handleNotification(_ notification: Notification) {
// 알림 처리
}
}
extension Notification.Name {
static let someNotification = Notification.Name("someNotificationName")
}
요약
순환 참조를 해결하기 위해서는 주로 약한 참조(Weak Reference)나 비소유 참조(Unowned Reference)를 사용하거나, 델리게이트 패턴에서 델리게이트 프로퍼티를 약한 참조로 선언하는 등의 방식으로 해결할 수 있다. 또한, 클로저와 NotificationCenter 사용 시에도 약한 참도를 사용하여 순환 참조를 방지할 수 있다. 이러한 방법들로 메모리 누수를 방지하고 객체가 적절히 메모리에서 해제될 수 있도록 할 수 있다.
우산과 우산 고리에 비유해서 이해가 쉽게 설명해보자면,
우산 비유에서 고리들이 서로를 걸어두는 방식은 객체들이 서로를 참조하는 방식과 같다. 약한 고리는 쉽게 풀릴 수 있는 고리로 상대방이 사라질 때 자동으로 풀리며, 비소유 고리는 상대방이 항상 있을 거라고 확신할 때 사용하는 고리이다. 이렇게 순환 참조를 해결하면 객체들이 메모리에서 적절히 해제되어 메모리 누수를 방지할 수 있다.
Swift의 ARC? 강한 참조? 약한 참조? | 참조가 강하고 약하기도 한다고...?
Swift의 ARC? 강한 참조? 약한 참조? | 참조가 강하고 약하기도 한다고...?
ARC에 대한 이해ARC(Automatic Reference Counting)ARC는 Swift에서 메모리 관리를 자동으로 진행해주는 기술인데, 클래스 인스턴스가 더 이상 필요하지 않을 경우 메모리에서 자동으로 해제해줍니다. 작
helldev-world.tistory.com
'걔 (개발)로그 > Swift' 카테고리의 다른 글
| Swift의 private static 조합 (0) | 2024.08.05 |
|---|---|
| Swift의 override, 재정의, 상속··· 그게 다 뭔 소리람 (0) | 2024.08.05 |
| Swift의 ARC? 강한 참조? 약한 참조? | 참조가 강하고 약하기도 한다고...? (0) | 2024.05.19 |
| Swift의 메모리 구조(Code, Data, Heap, Stack) | 도대체 어떻게 생겼나 봅시다. (0) | 2024.05.19 |
| Swift에서 Filter와 Map (0) | 2024.05.17 |