Sept 01, 2021, TIL (Today I Learned) - 게을러야하는이유[근거 있는 lazy var 사용]

Inwoo Hwang·2021년 9월 1일
0

TIL

목록 보기
4/8
post-thumbnail

학습내용

무분별한 lazy를 지양하기 위해 모든 프로퍼티를 건드리다가 오늘 하나 더 배워가는부분이 있어서 글 남겨봅니다.

문제점: navigation item을 텝해도 정상적으로 작동하지 않는 문제

기존에 private lazy var로 선언했던 UIRightBarButtonItem 프로퍼티에서 lazy를 제거 하니 네비게이션바 우측상단의 + 버튼이 탭이되긴 하지만 다음 페이지로 넘어가지 않는 문제를 직면하였습니다.

private var UIRightBarButtonItem: UIBarButtonItem = {
        let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapAddButton))
        return addItem
    }()

private lazy var UIRightBarButtonItem: UIBarButtonItem = {
        let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapAddButton))
        return addItem
    }()

원래는 아래와 같이 상품판매 페이지로 넘어가야 하는데 말이죠....

디버깅 해봅시다.

뭐가 문제인지 확인하기 위해 해당 프로퍼티에의 #selector 에서 호출하는 메서드를 디버깅 해 봤어요.

@objc private func didTapAddButton(_ sender: UIBarButtonItem) {
        let openMarketItemViewController = OpenMarketItemViewController()
        navigationController?.pushViewController(openMarketItemViewController, animated: true)
    }

그냥 var인 경우!!

lazy var인 경우!

보시면 알겠지만 타겟에 self(OpenMarketViewController)가 있어야 하는데 var 로 구현한 경우 targetnil 이 할당되어 있더라구요.

그래서 주변분들께 질문을 하였고 깨달음을 얻은 점을 바탕으로 글을 남겨봅니다.

문제점을 작성하기 이전에 프로퍼티에 클로저 또는 메서드를 할당하는 방식에 대해서 간단하게 작성 해 보려 합니다.

Setting a Default Property Value with a Closure or Function

The Swift Programming Language에 따르면 저장 프로퍼티의 값이 복잡한 계산을 필요로 한다면 클로저 또는 전역함수를 통해서 특정 프로퍼티의 기본값을 셋업 해 줄 수 있습니다. 저희가 자주 사용하는 View또한 여러 연산작업이 필요한 경우가 있기에 자주 이와 같은 형식으로 작성하곤 하죠.

private lazy var UIRightBarButtonItem: UIBarButtonItem = {
        let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapAddButton))
        return addItem
    }()

클로저랑 이 문제랑 어떤 관계가 있길래?

클로저를 인스턴스 프로퍼티에 할당하는 부분에서 주의해야 하는 사항도 적혀 있는데요. 이게 바로 핵심입니다.

클로저를 사용하여 프로퍼티를 초기화 하는 경우, 해당 클로저가 실행되는 시점에서 해당 프로퍼티를 가진 인스턴스가 완전히 초기화되지 않았다는 것을 기억해야 합니다. 이것은 언급한 클로저 안에서 인스턴스의 다른 프로퍼티에 접근하지 못한 다는 것을 의미합니다. 또한 self 또는 인스턴스의 메서드 또한 클로저 내부에서 사용이 불가능합니다.

여기서 말하는 인스턴스에 ViewController 를 대입하면 조금 더 이해가 될거라 생각합니다. 클로저를 실행하여 ViewController에 할당할 때는 해당 ViewController는 초기화가 완전히 되지 않는 시점인 것입니다. 그렇기 때문에 ViewController 자기자신(self)를 포함한 메서드와 다른 프로퍼티에 접근이 불가능한 것이지요. 아직 ViewController가 제대로 태어나지도 못하는데 어떡하겠어요 ㅎㅎ.

그렇기 때문에

final class OpenMarketViewController: UIViewController {

	private var rightBarButtonItem: UIBarButtonItem = {
       	    let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(didTapAddButton))
            return addItem
    }()
}

해당 코드가 정상적으로 실행이 되지 않는 것입니다. 아직 OpenMarketViewController가 정상적으로 초기화 되지 않았기 때문에 addItemUIBarButtonItem 인스턴스를 할당하려 해도 버튼 아이템의 프로퍼티인 target에 ViewController 자기 자신을 할당하지 못하는거죠. 그렇기 때문에 target에 nil 값이 할당 되는 것이고 이 rightBarButtonItem은 무엇을 타겟으로 어떤 #selector 메서드를 호출해야 하는지 길을 잃게 되는거죠.

그럼 lazy에서는 왜 되는데?

lazy 저장 프로퍼티 같은 경우 저번 포스팅에서도 언급했던 경우 특정 인스턴스에 의존적인 프로퍼티입니다. 따라서 위 프로퍼티 같은 경우 self가 ViewController인 것을 인지하는 시점에 값이 계산되고 메모리에 올라오기 때문에 문제 없이 작동했던 것이죠. 그렇기 때문에 이와 같은 경우에는 lazy 프로퍼티를 사용하는게 적절한듯 싶습니다!! 😁

마치며

앱개발의 세계는 파면 팔수록 오묘하고 어렵다는 것을 거듭느끼게 됩니다. 시점의 중요성은 정말 매일매일 강조해도 모자랄 지경이네요. lazy를 쓰면 안좋다가 역시 아니라 적제적소에 사용해야 한다 오늘 다시 한 번 더 느끼게 되고 또 이런 문제점을 그냥 넘기지 않고 항상 파악하고 남기자 다짐하며 오늘 글은 여기까지 쓰겠습니다. 혹시라도 틀린점이 있거나 궁금하신 점이 있으면 언제든 글 남겨주세요:)

profile
james, the enthusiastic developer

0개의 댓글