iOS

Swift Retain Cycle

@서비 2022. 4. 25. 22:21

1. reference counter

 

메모리 누수를 막고 free를 안 해도 자동으로 메모리를 삭제 하도록 고안해 낸 방법이 reference counter인데 인스턴스 객체를 생성할 때, 내부에 레퍼런스 카운터를 두어서 자기 자신을 참조할 때마다 카운터를 증가시켜서 해당 카운터가 0 이 될 때, 스스로 삭제를 하는 방식이다.

 

 

 

2. 문제 발생

 

이러한 방식은 따로 리소스를 free 해 줄 필요가 없어서 아주 편리한데, 한가지 문제가 있다. 만약 객체 내부의 변수가 다른 객체 내부의 변수를 서로 참조하고 있다면, 해당 리소스는 free가 되지 않는다. 아래와 같은 코드에서 A와 B를 nil로 바꾸어 준다고 해도 내부 변수가 서로를 참조하고 있어서 객체가 사라지지 않는다. -> memory leak

class TestClass{
    var testClass: TestClass? = nil
    init(){
        print("init")
    }
    deinit(){
        print("deinit")
    }
}


var A : TestClass = TestClass()
var B : TestClass = TestClass()

A.testClass = B
B.testClass = A

A = nil
B = nil

 

 

 

3. 해결 방법

 

아주 간단 하다 변수 앞에 weak를 선언하면 된다. weak는 참조하되 카운터는 증가시키지 않는다. swift 에는 unowned 도 있는데 생각하지 말자. 일반적이지도 않고 실수하기 쉽다.

 weak var testClass: TestClass? = nil

 

 

 

4. 자주 쓰이는 형태

 

이론도 중요 하지만 무심코 사용하는 코드들에 이러한 부분이 다 녹아 있다. 아래 예시를 참조하자

 

4-1. delegate 패턴, 

delegate 도 사실상 해당 Class를 참조 하고 있으므로 weak로 선언하자.

class TestClass{
	weak var delegate: ChildViewControllerProtocol?

	func TestFunc(){
    	delegate.xx()
    }
}

 

 

 

4-2 closures

객체 내부에서 서로 참조 하는경우 가 있는데 주로 closures이고 이 경우 weak self로 참조하자.

 

class TestClass{
    weak var aBlock: (()->())? = nil
    let aConstant = 5
    
    func testFunc(){
        aBlock = {[weak self]
            print(self.aConstant)
    }
}

 

 

5. detecting

class 내부에 아래와 같은 함수를 두어 print 가 되는지를 보고 detecting 하면 된다.

deinit(){
    print("deinit")
}

 

 

 

 

참고:

https://fomaios.tistory.com/entry/iOS-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-Retain-Cycle%EC%9D%B4%EB%9E%80

https://baked-corn.tistory.com/30