오늘은 오랜만에 코드리뷰를 포스팅해본다.
고마워요 붱이🦉
오늘은 Delegate, ARC와 관련 있는 부분이다.
어쩌면 class까지도..?
내가 작성한 코드를 먼저 보자.
protocol TestDelegate {
func test()
}
class TestClass {
lazy var delegate: TestDelegate? = nil
}
지금 보면 웃긴뎈ㅋㅋ
delegate
를 사용하고 싶었다...ㅋㅋ
해당 코드에 관련한 리뷰들도 한 번 보자.
Q. 델리게이트와 리테인 사이클에 대해 한번 알아보시면 좋을 것 같습니다 🙇♂️
Q. lazy 라는 키워드를 사용하신 이유에 대해서도 궁금합니다 🙃
Q. UIKit 의 델리게이트 프로토콜을 살펴보면 참조타입일때만 프로토콜을 채택할 수 있는 제한을 볼 수 있습니다 어떤 이유 일까요?
우선 두번째 리뷰부터 살펴보자.
내가 lazy
키워드를 사용했던 이유는 헷갈려서.. 였다...
지금이라면 안 그럴거 같은데 헷갈리면 찾아 봐야한다...
lazy
키워드에 대해서는 여기서 다뤘었다.
간단힌 말하자면 lazy
는 저장 프로퍼티 혹은 클로저와 결합할 수 있다.
이름 그대로 게으른/지연의 의미를 가지고 있다.
즉, 호출할때에 인스턴스가 생성된다는 의미이다.
따라서 lazy
로 선언된 변수는 초기에는 값이 존재하지 않고, 변수가 호출되어야만 초기 값이 세팅되기 때문에 var
와 함께 선언되어야 한다.
struct
와 class
에서만 사용할 수 있다.
따라서 delegate
에 lazy
키워드는 안 써도 됐다...ㅋㅋ
그렇다면 lazy
가 아닌 다른거 무엇을 써야할까?
delegate
와 retain 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 공식 문서에서도 강한 순환 참조를 깨는 방법으로 weak
와 unowned
를 소개하고 있다.
둘의 가장 큰 차이는 weak
는 rc가 0이 되면 변수에 nil을 지정해준다는 점이고, unowned
는 그렇지 않다는 점이다.
Swift5 이전까지는 unowned
는 반드시 값이 nil이 아님을 보장할때 사용해야 했지만 Swift5부터는 옵셔널을 할당할 수 있다.
공식 문서에서는 unowned Optiona
은 nil을 할당할 수 있다는 점을 빼면 unowned
와 같은 프로세스를 가지고 있고, ARC가 메모리를 해제하는 것을 방해하지 않는다고 한다.
그렇다면 왜 delegate
에 대해서 알아보면 weak
와 쓰라고 하는걸까?
unowned
를 사용하면 퍼포먼스면에서도 좋다고 생각되는데...
delegate
에 weak
를 사용하는 이유는 명확하게 알겠는데 unowned
를 사용해도 되는지 혹은 unowned
와 delegate
를 같이 사용하면 유의해야하는 점이 있는지 궁금하다.
protocol TestDelegate: class {
func test()
}
2번째 리뷰와 관련한 코드에서도 delegate
를 class로 제한한 모습을 볼 수 있다.
class를 쓰지 않는다면 해당 타입이 클래스인지 구조체인지 알 수 없어 weak
, unowned
키워드를 사용할 수 없다.
오늘은 ARC에 대해 공부한 김에 미뤄뒀던 코드리뷰에 대해 포스팅했다.
리뷰에 대해 모두 답을 할수는 있지만 아직 풀리지 않는 궁금증이 있다...
너무 궁금해...
알게되면 추가해야지..
그럼 이만👋