본문 바로가기
iOS/Swift

순환참조

by hong7 2024. 5. 23.

strong 강한참조

인스턴스의 주소값이 변수에 할당될 때,

RC가 증가하면 강한참조

strong을 별도로 선언하지 않아도, 디폴트값이 strong

stong에는 순환참조 문제가 있음..

순환참조

두개의 객체가 서로가 서로를 참조하고 있는 형태

ARC의 단점.. 강한 순환참조 발생 시 영구적으로 메모리가 해제되지 않을 수 있음

강한 순환참조 예시 ⬇️

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

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

Person 에게는 Apartment 타입의 프로퍼티(apartment)가 있고,

Apartment 에게는 Person 타입의 프로퍼티(tenant)가 있음

(존재할수도 존재하지 않을수도 있으니 옵셔널타입)

var hong: Person? = Person(name: "hong")  // RC of hong = 1
var unit4A: Apartment? = Apartment(unit: "4A")  // RC of unit4A = 1

// 강한 순환참조 발생 예시
hong?.apartment = unit4A  // RC of unit4A = 2 (hong이 unit4A를 강하게 참조)
unit4A?.tenant = hong  // RC of hong = 2 (unit4A가 hong을 강하게 참조)

// 객체 해제 시도
hong = nil  // RC of hong = 1 (unit4A가 **여전히 hong을 참조**)
unit4A = nil  // RC of unit4A = 1 (hong이 **여전히 unit4A를 참조**)

Person과 Apartment 클래스의 deinit 메서드가 호출되지 않는다..

why❓

현재 시점에서 hong과 unit4A 모두 참조 카운트가 1로 남아있으며,

이는 서로가 서로를 강하게 참조하고 있기 때문

따라서, 두 객체 모두 메모리에서 해제되지 않는다.

memry leak 발생 어떻게 해결하냐?

weak, unowned를 사용

weak 약한참조

참조 카운트가 증가하지 않으며,

참조 대상이 해제되는 경우 자동으로 nil로 설정

(nil로 설정된다는걸로 보아 weak는 무조건 옵셔널 타입의 변수)

강한 순환참조 해결방법? 둘 중 한 쪽을 weak로 선언

어느쪽을 weak로 선언할까? 둘 중 수명이 더 짧은 인스턴스를 가리키는 것약한 참조로 선언

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

class Apartment {
    let unit: String
    weak var tenant: Person? // ⭐️ 약한 참조로 변경
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment Deinit")
    }
}
var hong: Person? = Person(name: "hong")  // RC of hong = 1
var unit4A: Apartment? = Apartment(unit: "4A")  // RC of unit4A = 1

// 강한 순환 참조 해결
hong?.apartment = unit4A  // RC of unit4A = 2 (hong이 unit4A를 강하게 참조)
unit4A?.tenant = hong  // RC of hong = 1 (unit4A가 hong을 약하게 참조)

// 객체 해제 시도
hong = nil  // RC of hong = 0 (해제됨, unit4A는 더 이상 hong을 참조하지 않음)
unit4A = nil  // RC of unit4A = 0 (해제됨)

이제 hong과 unit4A 모두 정상적으로 메모리에서 해제

unowned 미소유참조

weak와 마찬가지로 참조 카운트를 증가시키지 않음

but!! 참조 대상이 해제되더라도 nil로 설정되지 않음

참조 대상이 항상 존재한다고 확신할 수 있을 때 사용

즉, 참조하던 인스턴스가 메모리에서 해제된 경우

nil을 할당받지 못하고 해제된 메모리 주소값을 계속 들고 있음!!

예시코드(Person이 Apartment보다 오래산다는 가정)

class Person {
    let name: String
    unowned var apartment: Apartment? // ⭐️ 미소유 참조로 변경
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Person Deinit")
    }
}

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

unowned가 붙은 Person의 apartment는

Person 인스턴스가 메모리에서 해제되기 전까진

절대 먼저 해제되어서는 안됨

(Person이 먼저 죽고 이후에 Apartment가 죽어야함)

어느쪽을 unowned로 선언할까?

둘 중 수명이 더 긴 인스턴스를 가리키는 애를 미소유 참조로 선언함

(참조 대상이 항상 존재한다고 확신!!할 수 있을 때 사용)

만약 Apartment가 먼저 메모리에서 해제된다면?

weak의 경우, 자동으로 apartment의 값이 nil로 지정되겠지만

hong?.apartment // nil

unowned의 경우, nil을 할당받지 못해 이미 해제된 메모리 주소값을 계속 들고있음

hong?.apartment // ❌ Error ❌

이미 메모리에서 해제된 포인터 값에 접근하려 해서 에러가 발생

'iOS > Swift' 카테고리의 다른 글

고차함수  (0) 2024.08.10
동시성프로그래밍  (0) 2024.05.24
프로세스, 쓰레드  (0) 2024.05.23
ARC  (0) 2024.05.23
메모리구조  (0) 2024.05.21