iOS 개발자를 위한 Swift 메모리 기초 개념 완벽 정리

작성일 :

iOS 개발자를 위한 Swift 메모리 기초 개념 완벽 정리

iOS 앱 개발에서 성능 최적화와 안정적인 동작을 보장하기 위해 메모리 관리는 매우 중요한 요소입니다. Swift는 메모리 관리를 자동으로 처리하는 Automatic Reference Counting (ARC)를 사용하지만, 기본 개념을 이해하지 못하면 메모리 누수나 성능 저하 등의 문제를 겪을 수 있습니다. 이 글에서는 Swift 메모리 관리의 기초 개념과 실용적인 예제를 통해 중요한 사항들을 정리하겠습니다.

메모리 관리 기초 개념

Swift의 메모리 관리는 객체가 생성되고 해제되는 과정을 효율적으로 관리하기 위해 ARC를 사용합니다. ARC는 각 객체에 대한 참조(reference) 카운트를 관리하여 객체가 더 이상 필요 없을 때 메모리에서 해제하도록 합니다. 기본적인 개념은 다음과 같습니다:

  1. 강한 참조 (Strong Reference): 객체를 소유합니다. 즉, 강한 참조가 있는 한 객체는 메모리에서 해제되지 않습니다.
  2. 약한 참조 (Weak Reference): 객체를 소유하지 않습니다. 약한 참조가 있는 객체는 다른 강한 참조가 모두 사라지면 해제될 수 있습니다.
  3. 미소유 참조 (Unowned Reference): 객체를 소유하지 않지만 약한 참조와 달리 참조하는 객체가 해제된 이후에도 nil이 되지 않습니다. 만약 해제된 객체에 접근하려 하면 런타임 오류가 발생합니다.

Automatic Reference Counting (ARC)

ARC는 객체의 생성과 해제 주기를 자동으로 관리하여 메모리 누수를 방지합니다. ARC는 객체의 참조 카운트를 기반으로 동작하며, 객체가 생성될 때마다 참조 카운트가 1 증가하고, 참조가 해제될 때마다 1 감소합니다. 참조 카운트가 0이 되면 객체가 메모리에서 해제됩니다.

예제 코드

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("
        (name) is being initialized")
    }
    deinit {
        print("
 (name) is being deinitialized")
    }

var person1: Person? = Person(name: "John")
var person2: Person? = person1
var person3: Person? = person1

person1 = nil
person2 = nil
person3 = nil

이 예제에서 객체 Personperson1, person2, person3이라는 변수에 의해 참조됩니다. 세 참조가 모두 nil로 설정되면 ARC는 객체가 더 이상 필요하지 않다고 판단하고 메모리에서 해제합니다.

메모리 누수 방지: 강한 순환 참조 (Strong Reference Cycles)

ARC는 대체로 잘 작동하지만, 강한 순환 참조(Strong Reference Cycles) 문제를 발생시킬 수 있습니다. 이는 두 개 이상의 객체가 서로를 강하게 참조하여 메모리에서 해제되지 않는 상황을 말합니다.

예제 코드

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    var apartment: Apartment?
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    var tenant: Person?
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john

john = nil
unit4A = nil

이 예제에서 johnunit4A는 서로를 강하게 참조하므로 참조 카운트가 0이 되지 않아 메모리에서 해제되지 않습니다.

약한 참조와 미소유 참조로 강한 참조 방지

강한 참조 순환을 방지하기 위해 약한 참조(weak)와 미소유 참조(unowned)를 사용합니다. 약한 참조는 객체의 참조 카운트를 증가시키지 않으며, 객체가 해제될 때 nil로 설정됩니다. 미소유 참조는 객체가 해제된 이후에도 참조를 유지하지만, 해제된 객체에 접근하려 하면 오류가 발생합니다.

예제 코드

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    var apartment: Apartment?
    deinit {
        print("
 (name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
}
    weak var tenant: Person?
    deinit {
        print("
 (unit) is being deinitialized")
}
}

var john: Person? = Person(name: "John")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john

john = nil
unit4A = nil

이 예제에서는 tenant 속성을 약한 참조로 선언하여 강한 참조 순환을 방지합니다. 결과적으로 johnunit4A는 모두 안전하게 메모리에서 해제됩니다.

클로저와 메모리 관리

클로저와 같은 비동기 작업에서 메모리 관리 또한 중요한 요소입니다. 클로저는 강한 참조를 발생시키므로 클로저 내부에서 자신을 캡처할 때 강한 참조 순환이 발생할 수 있습니다. 이를 방지하기 위해 클로저 내부에서 캡처 리스트를 사용하여 약한 참조를 정의할 수 있습니다.

예제 코드

swift
class SomeClass {
    var someProperty: String = "Hello"
    lazy var someClosure: () -> String = { [weak self] in
        return self?.someProperty ?? ""
    }
}

let instance = SomeClass()
let result = instance.someClosure()
print(result)

이 예제에서는 클로저 내부에서 self를 약한 참조로 캡처하여 강한 참조 순환을 방지합니다.

결론

Swift의 메모리 관리는 ARC를 통해 자동으로 처리되지만, 기본 개념을 이해하고 강한 참조 순환을 방지하기 위한 올바른 접근법을 사용하는 것이 중요합니다. 강한 참조, 약한 참조, 미소유 참조 및 클로저 캡처 리스트와 같은 개념을 잘 이해하여 효율적이고 안정적인 iOS 앱을 개발할 수 있습니다.