메모리 누수는 개발자들이 종종 직면하는 문제입니다. 메모리 누수가 발생하면 앱의 성능과 안정성에 영향을 미칠 수 있으므로, 신속하게 해결해야 합니다. 이번 글에서는 Swift에서 메모리 누수가 발생할 수 있는 주요 예외 상황과 그에 대한 대처 방법에 대해 알아보겠습니다.
1. 강한 순환 참조
강한 순환 참조는 메모리 누수를 초래할 수 있는 주요 원인 중 하나입니다. 강한 순환 참조는 서로에 대한 강한 참조를 유지하면서 객체들이 서로를 참조하는 상황을 의미합니다.
class Person {
var name: String
var pet: Pet?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 객체가 해제되었습니다.")
}
}
class Pet {
var name: String
var owner: Person?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 객체가 해제되었습니다.")
}
}
var john: Person? = Person(name: "John")
var dog: Pet? = Pet(name: "Dog")
john?.pet = dog
dog?.owner = john
john = nil
dog = nil
위의 예제에서 Person
과 Pet
클래스는 서로에 대한 강한 참조를 유지하고 있습니다. 이 경우 john
과 dog
객체가 해제되지 않고 계속해서 메모리에 남을 수 있습니다.
해결 방법으로는 강한 참조를 약한 참조로 변경하는 것입니다. Swift에서는 weak
또는 unowned
키워드를 사용하여 약한 참조를 만들 수 있습니다. 위의 예제에서 owner
의 참조를 약한 참조로 변경하면 다음과 같습니다.
class Person {
var name: String
weak var pet: Pet?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 객체가 해제되었습니다.")
}
}
class Pet {
var name: String
unowned var owner: Person
init(name: String, owner: Person) {
self.name = name
self.owner = owner
}
deinit {
print("\(name) 객체가 해제되었습니다.")
}
}
var john: Person? = Person(name: "John")
var dog: Pet? = Pet(name: "Dog", owner: john!)
john?.pet = dog
john = nil
dog = nil
이제 owner
참조는 약한 참조로 선언되었고, owner
가 해제되면 pet
도 자동으로 해제됩니다.
2. 클로저와 강한 참조 사이클
클로저는 메모리 누수의 원인이 될 수 있는 또 다른 요소입니다. 클로저 내부에서 self
에 대한 강한 참조를 유지하고 있다면, 클로저가 해제되지 않는 한 self
역시 해제되지 않게 됩니다.
class MyClass {
var number = 0
var closure: (() -> Void)?
init() {
closure = { [weak self] in
self?.number = 10
print(self?.number ?? 0)
}
}
deinit {
print("MyClass 객체가 해제되었습니다.")
}
}
var myObject: MyClass? = MyClass()
myObject?.closure?()
myObject = nil
위의 예제에서 closure
내부에서 self
를 약한 참조로 가져와 메모리 누수를 방지합니다. myObject
를 해제할 때는 closure
도 함께 해제됩니다.
3. NotificationCenter 관리
NotificationCenter를 사용할 때 주의해야 할 점도 있습니다. NotificationCenter에 옵저버를 등록하면 해당 옵저버는 사용하지 않을 때 명시적으로 해제해주어야 합니다. 그렇지 않으면 메모리 누수가 발생합니다.
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), name: NSNotification.Name("MyNotification"), object: nil)
}
@objc func handleNotification() {
// Notification 처리 로직
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
위의 예제에서는 viewDidLoad()
에서 NotificationCenter에 옵저버를 등록하고, deinit()
에서 옵저버를 해제해주고 있습니다.
결론
메모리 누수는 iOS 앱 개발에서 흔히 발생하는 문제입니다. 본 글에서는 강한 순환 참조, 클로저와 강한 참조 사이클, NotificationCenter 관리 등에서의 메모리 누수를 방지하는 방법에 대해서 알아보았습니다. 이러한 예외 상황들을 잘 관리하여 앱의 성능과 안정성을 보장할 수 있도록 해야 합니다.