[swift] 메모리 누수 예외

메모리 누수는 개발자들이 종종 직면하는 문제입니다. 메모리 누수가 발생하면 앱의 성능과 안정성에 영향을 미칠 수 있으므로, 신속하게 해결해야 합니다. 이번 글에서는 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

위의 예제에서 PersonPet 클래스는 서로에 대한 강한 참조를 유지하고 있습니다. 이 경우 johndog 객체가 해제되지 않고 계속해서 메모리에 남을 수 있습니다.

해결 방법으로는 강한 참조를 약한 참조로 변경하는 것입니다. 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 관리 등에서의 메모리 누수를 방지하는 방법에 대해서 알아보았습니다. 이러한 예외 상황들을 잘 관리하여 앱의 성능과 안정성을 보장할 수 있도록 해야 합니다.

참고 자료