[Sesac IOS] 45일차 TIL
TIL

[Sesac IOS] 45일차 TIL

45일차 내용을 정리한 글입니다.


Learned

ARC(Automatic Reference Counting)

1) 개요

  • Swift에서 앱의 메모리를 관리하는 시스템으로서, 참조 타입 인스턴스의 참조 횟수를 통해
    자동으로 메모리에서 해지하거나 유지함.
  • 참조 횟수에 대한 관리는 참조 타입(클래스, 클로저)의 인스턴스에만 적용되고
    값 타입(구조체, 열거형)에는 적용되지 않음.
  • 인스턴스가 생성되면 ARC는 적절한 크기의 메모리를 할당하여 인스턴스에 대한 정보와
    저장 프로퍼티의 값을 담는데, 참조 횟수가 0이되면 ARC는 해당 인스턴스가 더 이상
    사용되지 않는다고 판단하여 메모리에서 해지하고 공간을 확보함.
  • 또한 해당 인스턴스에 대한 참조 횟수가 최소 하나라도 존재한다면 메모리에서 해지되지 않음.
NOTE
Swift 이전에 Objective-C에서는 MRC(Manual Reference Counting)로 메모리를 관리했다.

2) 강한 참조 순환 (Strong Reference Cycle)

  • 클래스의 인스턴스 간에 서로를 강하게 참조하고 있는 경우를 의미함.
  • 예를 들어 두 클래스 타입의 인스턴스가 서로를 참조하고 있는 상태에서
    인스턴스에 접근할 방법이 없어지면 참조 횟수가 줄어들지 않고 유지되어
    사용하지 않지만 메모리 공간을 차지하는 메모리 누수가 발생할 수도 있음.
  • 따라서 이에 대한 해결 방법으로 weak 참조와 unowned 참조가 존재함.

각 인스턴스를 참조하고 있는 john, unit4A 프로퍼티에 nil을 할당하면
Person 인스턴스의 apartment 프로퍼티와 Apartment 인스턴스의 tenant 프로퍼티가
각 인스턴스를 참조하고 있어 참조 횟수가 유지되기 때문에
메모리에서 해지되지 않고 메모리 누수가 발생함.

3) 약한 참조 (Weak Reference)

  • 약한 참조로 선언한 프로퍼티는 참조하는 인스턴스의 참조 횟수에 영향을 주지 않으며
    참조하고 있는 인스턴스가 메모리에 존재하지 않을 때 nil 값을 할당함.
NOTE
약한 참조로 선언한 프로퍼티는 nil값을 가질 수 있기 때문에 옵셔널 타입으로 선언해야 한다.
  • 참조하는 인스턴스가 먼저 메모리에서 해지될 때 약한 참조를 사용함.

Apartment 인스턴스의 tenant 프로퍼티가 약한 참조로 선언됐기 때문에 john 변수에 nil을 할당하면
Person 인스턴스의 참조 횟수가 0이 되어 ARC에 의해 메모리에서 해지된다.
NOTE
약한 참조로 선언된 프로퍼티에 nil을 할당하면 프로퍼티 옵저버는 실행되지 않는다.

4) 미소유 참조 (Unowned Reference)

  • 미소유 참조는 약한 참조와 동일하게 참조하는 인스턴스(참조 대상)의 참조 횟수에 영향을 주지 않지만
    참조 대상의 생명주기가 자신과 같거나 자신보다 더 길 때 사용함.
  • 미소유 참조는 참조하는 대상(인스턴스)이 항상 메모리에 존재한다고 생각하기 때문에
    실제로 인스턴스가 메모리에 존재하지 않더라도 nil값을 반환하지 않음.
  • 따라서 만약 참조 대상이 메모리에 존재하지 않을 때 참조하려고 하면 런타임 에러가 발생함.

1. CreditCard 인스턴스의 customer 프로퍼티가 미소유 참조로 선언됐기 때문에
john에 nil을 할당하면 Customer 인스턴스에 대한 참조 횟수가 0이 되어 메모리에서 해지된다.
2. Customer 인스턴스가 해지되면 자동으로 CreditCard 인스턴스에 대한
참조 횟수도 0이 되므로 CreditCard 인스턴스 역시 메모리에서 해지된다.

→ customer 프로퍼티 입장에서 참조하는 대상(Customer 인스턴스)의 생명주기가 자신과 같음.

5) 클로저에서의 강한 참조 순환

  • 클로저에서 외부 변수를 사용할 때 내부적으로 값을 저장하는 캡쳐가 일어나기 때문에
    강한 참조 순환이 발생할 수 있음.
  • 클로저는 값을 캡쳐할 때 참조를 사용하기 때문에 참조 대상의 참조 횟수에 영향을 줌.
NOTE
클로저 안에서 참조 타입의 인스턴스를 여러번 참조하더라도 실제로는 단 한번의 강한 참조만 캡쳐된다.
  • 따라서 캡쳐 리스트를 사용해 캡쳐 참조에 약한 참조 또는 미소유 참조를 지정(참조 타입)하거나
    값 타입으로 캡쳐(값 타입)해서 강한 참조 순환 문제를 해결할 수 있음.
  • 특히 클래스에서 delegate를 사용할 때 weak로 선언해서 강한 참조 순환을 해결해야 함.
NOTE
weak로 delegate를 선언할 때 프로토콜에 따라 오류가 발생할 수 있는데,
그 이유는 weak가 참조 타입에만 사용될 수 있는데 반해 프로토콜을 준수하는 타입은 참조 타입이 아닐 수 있기 때문이다.
따라서 delegate 프로토콜에 AnyObject 프로토콜을 상속하여 해결할 수 있다.
// 예시1 : 클로저 내부에서 사용하고자 하는 값을 복사해서 캡쳐
func myClosure() -> () -> () {
    var number = 100
    
    // 캡쳐 리스트로 값을 캡쳐하지 않으면 myClosure 함수 내부의
    // number 변수를 참조를 통해 가져옴
    let closure: () -> Void = { [number] in
        print("closure: \(number)")
    }
    return closure
}

let someClosure = myClosure()
someClosure()
// 예시2 : 미소유 참조를 사용해 self를 캡쳐 → 강한 참조 순환 해결
class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

1. HTMLElement 인스턴스 내부에서 클로저가 해당 인스턴스를 미소유 참조하고 있기 때문에
paragraph에 nil을 할당하면 HTMLElement 인스턴스의 참조 횟수가 0이 되어 메모리에서 해지된다.
2. 인스턴스가 해지될 때 자동으로 클로저의 참조 횟수도 0이 되서 메모리에서 해지된다.

→ 클로저 입장에서 참조하는 대상(HTMLElement 인스턴스)의 생명주기가 자신과 같음.

 


이상입니다.

'TIL' 카테고리의 다른 글

[Sesac IOS] 46일차 TIL  (0) 2022.09.09
[Sesac IOS] 43~44일차 TIL  (0) 2022.09.05
[Sesac IOS] 42일차 TIL  (0) 2022.08.31
[Sesac IOS] 41일차 TIL  (2) 2022.08.31
[Sesac IOS] 40일차 TIL  (0) 2022.08.31