iOS/문법

Swift 문법과 친해지기 - Strong, Weak, unowned, 순환 참조

HJ39 2023. 1. 7. 00:55

Strong - 강한 참조

강한 참조는 인스턴스가 할당되고 RC가 증가하면 강한 참조라고 한다.

default값이 강한 참조로 구성된다고 한다.

 

 

순환 참조

ARC의 단점으로 '순환 참조가 발생될 경우 영구적으로 메모리가 해제되지 않을 수 있다'가 있다.

 

□ 순환 참조 예시 1

class Man {
    var name: String
    var girlfriend: Woman?
    
    init(name: String) {
        self.name = name
    }
    deinit { print("Man Deinit!") }
}
 
class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
        self.name = name
    }
    deinit { print("Woman Deinit!") }
}

var chelosu: Man? = .init(name: "chelosu")
var yeonghee: Woman? = .init(name: "yeonghee")

다음과 같이 a와 b에 각각 하나씩 인스턴스가 생성되었을 때

 

□ 순환 참조 예시 2

chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu

다음과 같이 지정하는 경우에 메모리는 다음과 같이 된다.

힙 영역에서 RC값이 서로 참조가 되어 2가 되었다.

두 객체가 서로 참조하는 형태를 순환 참조라고 한다.

 

저 상태에서 cheolsu와 yeonghee의 값을 nil로 변경하면 힙 영역 내부의 인스턴스의 RC값은 1씩 줄어들게 되는데 0이 아닌 1이 되므로 영구적으로 메모리 속에 남아 있게 된다.

저런 경우에 인스턴스에 접근할 방법이 존재하지 않아서 메모리 해제를 할 수 없다.

 

이와 같은 문제를 해결하기 위해 나온 것이 weak, unowned 개념이다.

 

Weak

strong과 다르게 인스턴스를 참조할 경우 RC값을 증가시키지 않는다.

인스턴스가 메모리에서 해제된 경우 자동적으로 nil이 할당되어 메모리가 해제된다. → 옵셔널 타입의 변수로 선언해야 함

 

□ 약한 참조 예시 1

class Man {
    var name: String
    weak var girlfriend: Woman?
    
    init(name: String) {
        self.name = name
    }
    deinit { print("Man Deinit!") }
}
 
class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
        self.name = name
    }
    deinit { print("Woman Deinit!") }
}

var chelosu: Man? = .init(name: "chelosu")
var yeonghee: Woman? = .init(name: "yeonghee")

둘 중 한 개를 약한 참조를 하는 경우 RC값이 증가하지 않게 된다.

 

□ 약한 참조 예시 2

chelosu?.girlfriend = yeonghee
yeonghee?.boyfriend = chelosu

→ 강한 참조와 같다.

 

이와 같은 상황인 경우 

다음과 같이 Woman에서 Man은 강한 참조를 하지만 Man에서 Woman은 약한 참조를 해서 Woman의 RC이 증가하지 않는다.

이때 철수와 영희를 nil로 바꾸면

 

각 힙영역에 할당된 인스턴스들의 RC값이 1씩 감소했을 때

힙영역에 있던 Woman의 인스턴스 값은 RC가 0이 되어 메모리가 해제된다.

 

그 이후 Woman 인스턴스가 해제되고 Man의 RC값은 0이 되므로 메모리에서 해제된다.

weak 참조의 특징 중 하나인 가리키던 인스턴스가 메모리에서 해제된 경우 nil이 할당된다.

(깔끔하게 메모리에서 모두 해제되었다.)

 

 

Strong과 Weak 참조를 선택하는 방법

두 객체의 수명 사이클을 보고 더 짧은 수명을 가진 인스턴스를 가진 변수를 weak로 선언하면 된다.

 

 

unowned 참조 (미소유 참조)

weak와 unowned의 차이는 unowned는 인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신이 있는 경우 사용한다.

참조하던 인스턴스가 메모리에서 해제된 경우 nil을 할당받지 못하고 해제된 메모리 주소값을 계속 가지고 있는다.

 

weak는 참조하던 인스턴스가 메모리에서 해제된 경우 nil값을 반환하지만 unowned는 (인스턴스가 이미 해제된) 메모리 주소값을 가지고 있어 접근할 경우 오류를 발생시킨다.

 

만약 강한 순환 참조가 발생한 경우 

더 수명이 긴 인스턴스를 가리키는 변수에 unowned을 사용하면 해결할 수 있다.

 

하지만 unowned는 어떤 인스턴스가 먼저 사라질지 모르는 경우가 많으므로 확신이 없으면 사용을 하지 않는 것이 좋다.

 

소들이님 블로그에 가면 설명이 이해하기 쉽게 작성되어 있어 너무 좋다!

 

# 참고한 사이트

  1. https://babbab2.tistory.com/27