Swift/Swift 기본문법

Swift 메모리 관리 ARC(Automatic Reference Counting)

728x90

 C 나 C++ 에서는 OS 레벨의 메모리에 직접 접근하기 때문에 할당해준 메모리를 직접 해제해주었다.

그렇지 않으면 memory leak 문제가 발생 할 수 있는데 iOS에선 ARC 말그대로 참조 횟수를 카운트해서 메모리에서 해제해준다.

객체를 참조하는 수(Reference Count)가 0가 되면 메모리에서 해제해주는 방식이다.

 

자바에선 Garbage Collection으로 처리를 하는데, 프로세스가 메모리를 순환하면서 더 이상 사용 되지 않는다면 해제를 해주는 방법이다.

이런 방법은 언제 메모리에서 내려가는지 알 수가 없다는 특징이 있다.

ARC는 참조하는 카운트가 0가 되면 내려가기 때문에 내려가는 시점이 명확하다.

 

근데, 메모리를 내가 신경 쓴다는 것 자체가 불편하긴 하다.

과거에는 메모리가 적기 때문에 신경을 써줬지만, 굳이 메모리에 안내려가는 걸 신경 써야한다니 ..

 

ARC에 영향을 줄 수 있는 방법은 다음과 같다

- Strong (default, count를 셈)

- unowned (count를 세지 않음)

- weak (count를 세지 않음, optional로 변경함)

 

ARC에서 문제가 되는게 메모리 순환 문제가 존재한다. 객체가 서로 참조해서 Count가 1에서 안줄어드는 문제다.서로 참조하고 있으니 0이 될 수가 없어서 메모리에서 해제가 되지 않는 문제가 발생한다.

 

 

ARC 코드로 보기

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

Person이라는 클래스를 생성한다. deinit을 print를 통해 메모리에서 해제 됨을 알 수 있다.

 

var reference1: Person?
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John Appleseed")
// Prints "John Appleseed is being initialized"
reference2 = reference1
reference3 = reference1

refrence1에 Person이 할당되면서 처음으로 ARC가 하나 증가한다.

refrence2 , refrence3을 거치면서 ARC는 총 3이된다.

 

reference1 = nil
reference2 = nil

reference3 = nil
// Prints "John Appleseed is being deinitialized"

reference1, refrence2 를 nil로 변경하여도 referenc3이 참조하고 있기 때문에 deinit 메서드는 호출 되지 않는다. 

reference3이 nil이 되서야 모든 참조가 사라지고 ARC가 0가 되어 deinit 메서드를 호출하게 된다.

 

Strong Reference Cycles (ARC 메모리 순환문제)

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 }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil
unit4A = nil

위의 코드는 서로 참조하게 되어서 nil을 넣어줘도 deinit메서드가 호출되지 않는다.

 

이를 해결하기 위해서 위해서 본 ARC에 영향을 줄 수 있는 weakunowned가 존재한다.

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

강하게 참조하는 두개의 값 중 하나를 weak로 변경해주면 된다.

tenant의 Person은 메모리에 있어도되고 없어도 되는 optional로 변경된다.

그리고 참조 시에 count를 세지 않는다.

 

weak와 unowned의 차이점은 optional로 변경되는것이 아니기 때문에 unowned를 사용 했을 시에는 무조건 메모리에 tanant Person이 존재해야한다.

john을 nil로 변경하면 tanant는 더이상 가르키는게 없게되고, unit4A를 nil로 변경하면 메모리에서 내려오게 된다.

 

원래는 ios에서 closure내의 self가 순환 참조 문제를 일으키는걸 보고 문서작성을 시작했는데 생각보다 순환참조를 만들어내는게 쉽지는 않다.

반응형