Delegate와 Reference Counting

이원희·2021년 2월 20일
0

📱 iOS

목록 보기
21/24
post-thumbnail

오늘은 오랜만에 코드리뷰를 포스팅해본다.
고마워요 붱이🦉

오늘은 Delegate, ARC와 관련 있는 부분이다.
어쩌면 class까지도..?

Code

내가 작성한 코드를 먼저 보자.

protocol TestDelegate {
    func test()
}

class TestClass {
    lazy var delegate: TestDelegate? = nil
}

지금 보면 웃긴뎈ㅋㅋ
delegate를 사용하고 싶었다...ㅋㅋ

해당 코드에 관련한 리뷰들도 한 번 보자.

Q. 델리게이트와 리테인 사이클에 대해 한번 알아보시면 좋을 것 같습니다 🙇‍♂️
Q. lazy 라는 키워드를 사용하신 이유에 대해서도 궁금합니다 🙃
Q. UIKit 의 델리게이트 프로토콜을 살펴보면 참조타입일때만 프로토콜을 채택할 수 있는 제한을 볼 수 있습니다 어떤 이유 일까요?


Q. lazy 라는 키워드를 사용하신 이유에 대해서도 궁금합니다 🙃

우선 두번째 리뷰부터 살펴보자.

내가 lazy 키워드를 사용했던 이유는 헷갈려서.. 였다...
지금이라면 안 그럴거 같은데 헷갈리면 찾아 봐야한다...

lazy 키워드에 대해서는 여기서 다뤘었다.
간단힌 말하자면 lazy저장 프로퍼티 혹은 클로저와 결합할 수 있다.
이름 그대로 게으른/지연의 의미를 가지고 있다.
즉, 호출할때에 인스턴스가 생성된다는 의미이다.

따라서 lazy로 선언된 변수는 초기에는 값이 존재하지 않고, 변수가 호출되어야만 초기 값이 세팅되기 때문에 var와 함께 선언되어야 한다.
structclass에서만 사용할 수 있다.

따라서 delegatelazy 키워드는 안 써도 됐다...ㅋㅋ


Q. 델리게이트와 리테인 사이클에 대해 한번 알아보시면 좋을 것 같습니다 🙇‍♂️

그렇다면 lazy가 아닌 다른거 무엇을 써야할까?

delegateretain cycle에 주목해보자.
retain cycle은 클래스 인스턴스가 서로를 strong 참조로 잡고 있어 메모리에서 해제되지 않는 상황을 말한다.
그렇다면 delegate에서 retain cycle이 일어날까?

class TestViewController: UIViewController {
    var secondVC: SecondViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        secondVC = SecondViewController()
    }
}

class FirstViewConroller: UIViewController {
    var delegate: TestDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
}

class SecondViewController: UIViewController, TestDelegate {
    var firstVC: FirstViewConroller?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        firstVC = FirstViewConroller()
        firstVC?.delegate = self
    }
    
    func test() {
        
    }
}

protocol TestDelegate: class {
    func test()
}

참조 관계를 살펴보면 위와 같이 강한 참조 순환이 존재함을 알 수 있다.


현재 secondViewController의 rc가 2이므로 TestViewController 인스턴스가 메모리에서 해제되어도 rc는 1이 되어 SecondViewController와 FirstViewController의 인스턴스들은 메모리에서 해제되지 않는다.

그렇다면 이 부분을 어떻게 개선할 수 있을까?
강한 참조 순환에 대해 더 알고 싶다면 여기서 확인할 수 있다.

weak var delegate: TestDelegate?를 통해 해결할 수 있다.
weak로 참조 타입을 선언해줌으로써 강한 참조 순환을 깨준다.
강한 참조 순환이 발생하는 이유에 대해 생각해보면 Swift는 ARC에 의해서 rc(reference count)에 따라 인스턴스를 메모리에 적재/해제한다.

strong 참조는 rc를 +1하고, weak 참조는 rc를 증가시키지 않는다.
그렇다면 weak var delegate: TestDelegate?를 사용하면 참조관계는 어떻게 변할까?

SecondViewController의 rc가 1이 된다.
그렇다면 여기서 TestViewController의 인스턴스가 메모리에서 해제된다면 어떻게 될까?

SecondViewController의 rc가 0이 되어 메모리에서 해제될 것이다.
SecondViewController가 해제되면서 FirstViewController의 rc도 0이 되어 메모리 누수를 방지할 수 있게된다.

❗️나도 아직 명확하게 알지 못한 부분이 있다.

weak를 써야하는걸까?

Swift 공식 문서에서도 강한 순환 참조를 깨는 방법으로 weakunowned를 소개하고 있다.
둘의 가장 큰 차이는 weak는 rc가 0이 되면 변수에 nil을 지정해준다는 점이고, unowned는 그렇지 않다는 점이다.

Swift5 이전까지는 unowned는 반드시 값이 nil이 아님을 보장할때 사용해야 했지만 Swift5부터는 옵셔널을 할당할 수 있다.
공식 문서에서는 unowned Optiona은 nil을 할당할 수 있다는 점을 빼면 unowned와 같은 프로세스를 가지고 있고, ARC가 메모리를 해제하는 것을 방해하지 않는다고 한다.

그렇다면 왜 delegate에 대해서 알아보면 weak와 쓰라고 하는걸까?
unowned를 사용하면 퍼포먼스면에서도 좋다고 생각되는데...

delegateweak를 사용하는 이유는 명확하게 알겠는데 unowned를 사용해도 되는지 혹은 unowneddelegate를 같이 사용하면 유의해야하는 점이 있는지 궁금하다.


Q. UIKit 의 델리게이트 프로토콜을 살펴보면 참조타입일때만 프로토콜을 채택할 수 있는 제한을 볼 수 있습니다 어떤 이유 일까요?

protocol TestDelegate: class {
    func test()
}

2번째 리뷰와 관련한 코드에서도 delegate를 class로 제한한 모습을 볼 수 있다.
class를 쓰지 않는다면 해당 타입이 클래스인지 구조체인지 알 수 없어 weak, unowned 키워드를 사용할 수 없다.


마무리

오늘은 ARC에 대해 공부한 김에 미뤄뒀던 코드리뷰에 대해 포스팅했다.
리뷰에 대해 모두 답을 할수는 있지만 아직 풀리지 않는 궁금증이 있다...
너무 궁금해...
알게되면 추가해야지..
그럼 이만👋

0개의 댓글