lazy 키워드는 지연 저장 속성(Lazy Stored Property)을 선언하는데 사용된다. 이는 초기화 시점까지 계산이 필요하지 않은 복잡한 객체나 계산 비용이 큰 프로퍼티를 만들 때 특히 유용한다.
보다 쉽게 설명하자면, lazy는 "필요할 때가 오면 그 때 초기화 해"라고 알려주는 것이다.
lazy 키워드의 특징은 아래와 같다.
lazy 프로퍼티는 해당 프로퍼티에 처음 접근하는 순간에 메모리에 할당되고 초기화된다.(그 전까지는 초기화X)lazy는 값이 변할 수 있으므로 항상 var 키워드와 함께 사용해야 한다.lazy를 사용하면 앱의 시작 성능을 크게 개선할 수 있다.일반적인 저장 속성과의 차이를 아래 예시를 통해 쉽게 이해할 수 있다.
final class MyViewController: UIViewController {
let largeData: [String] = {
print("largeData 초기화")
return (0..<100000).map { "데이터 \($0)" } // 앱 시작 시 즉시 초기화
}()
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad 호출")
//...
}
}
let vc = MyViewController()
// 출력:
// largeData 초기화
// viewDidLoad 호출
위 코드에서는 MyViewController의 인스턴스가 생성되는 즉시 largeData가 초기화된다.
final class MyViewController: UIViewController {
lazy var largeData: [String] = {
print("largeData 초기화")
return (0..<100000).map { "데이터 \($0)" } // 처음 접근할 때 초기화
}()
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad 호출")
//...
}
}
let vc = MyViewController()
// 출력:
// viewDidLoad 호출
// vc.largeData에 처음 접근하는 순간 초기화
let firstItem = vc.largeData.first
// 출력:
// largeData 초기화
위 코드에서는 MyViewController의 인스턴스가 생성될 때 largeData가 초기화되지 않는다.
vc.largeData에 접근하는 시점에 비로소 초기화 클로저가 실행된다.
위의 예시를 보면 lazy를 사용하면 여러가지로 좋아 보인다.
무조건 초기화를 하는게 아니라 처음 호출되는 시점에 초기화를 한다면 성능에 이점이 많을 것 같은데, 그럼 왜 모든 프로퍼티에 대해 lazy 키워드를 사용하지 않는걸까?
그 이유는 대표적으로 초기화 시점의 불확실성, 성능 오버헤드, 코드의 가독성 저하를 주요 요인으로 들 수 있다.
lazy 프로퍼티는 처음 접근하는 시점에 초기화된다.
이는 런타임에 어떤 시점에서 메모리가 할당되고 계산이 이루어질지 예측하기 어렵게 만든다.
만약 여러개의 lazy 프로퍼티가 서로 의존하고 있거나, 복잡한 초기화 과정을 거친다면 의도치 않은 순서로 초기화되어 버그를 유발할 수 있다.
예를 들어, 뷰 컨트롤러의 뷰 계층이 아직 준비되지 않은 상태에서 lazy 프로퍼티가 초기화되면서 뷰의 크기를 참조하려 한다면, 값이 -이 되어 레이아웃 오류가 발생할 수 있다.
모든 프로퍼티에 lazy를 붙이면 코드를 읽는 개발자는 해당 프로퍼티가 언제 초기화될지 명확하게 알기 어렵다. 이는 코드의 흐름을 파악하는 데 방해가 되어 유지보수성을 떨어뜨리는 요인이 된다.
lazy 프로퍼티는 스레드에 아넞ㄴ하지 않을 수 있다. 여러 스레드에서 동시에 접근하면 초기화가 중복될 위험이 있고, 이를 방지하기 위해 추가적인 동기화(synchroniaztion) 코드가 필요할 수 있어 오히려 성능 오버헤드를 유발할 수 있다.
lazy는 다음과 같은 특정 상황에서만 사용해야 가장 효과적이다.
따라서 lazy는 모든 경우에 적용되는 만능 해결책이 아니다. 대부분의 프로퍼티는 앱의 초기 로딩 시점에 함께 초기화되는 것이 코드의 명확성을 높이고 예상치 못한 버그를 방지하는 데 더 효과적이기 때문이다.