[Swift] lazy property
목차)
1. lazy 란?
2. Swift의 lazy
1) var lazy
2) sequence or collection lazy
3. 적절한 lazy 사용하기
1) 주의 사항
4. 예시 코드
1. lazy 란?
'lazy loading' 이라고도 한다.
지연 초기화를 구현하는 키워드로 속성이 처음으로 필요한 시점에 초기화 될 수 있도록 한다.
불필요한 계산이나 자원소모를 피하고, 성능과 메모리 사용을 최적화 할 수 있다.
주로 비용이 많이 드는 작업이나, 자원소비가 큰 초기화를 지연시키기 위해 사용된다.
예를 들어 네트워크 요청이나 파일 로딩과 같은 작업은 필요한 시점에서만 수행 하도록,
'lazy loading' 을 수행하여 불필요한 작업을 방지 할 수 있다.
2. Swift 의 lazy
Swift 에서는 lazy를 두가지 방식으로 사용한다.
1) lazy var
인스턴스 변수를 지연 초기화 하는데 사용한다.
class MyClass {
lazy var myProperty: Int = {
// 복잡한 초기화 로직
return 1
}()
}
myProperty 에 접근할 때 변수에 값을 할당한다.
2) Sequence or Collection lazy
Swift 공식문서를 확인해보자.
A sequence containing the same elements as this esquence, but on which some operations, such as map and filter, ar implemented lazily
시퀀스와 동일한 요소를 포함하는 시퀀스로, map이나 filter 와 같은 연산이 게으르게(lazily) 구현된 것이다.
- Swift 공식문서 -
정리하면,
sequence protocol 을 따르는 각 요소에 접근 하여 변환 또는 연산 작업을 하는 method 에 적용할 수 있다는 것이다.
변환 또는 연산 작업을 하는 method 로는 map, filter, flatMap, compactMap, sorted, enumerated 등이 있다.
let numbers = [1, 2, 3, 4, 5]
//지연 map 연산
let doubledNumbers = numbers.lazy.map { $0 * 2 }
for number in doubledNumbers {
print(number)
}
sequence protocol 을 따르는 numbers 에 lazy 를 체이닝하여 적용하였다.
서비스 초기화 시점에 map() 연산을 하지 않으며, doubledNumbers 에 접근할 때 map() 연산을 실행하여 값을 할당한다.
3. 적절한 lazy 사용하기
lazy keyword 가 메모리를 최적화 해준다면, 모든 곳에 선언하면 좋을 것 같지만 그렇지 않다.
서비스가 초기화 되는 시점에 데이터를 연산하여 할당 해두면 필요 시점에 바로 출력 할 수 있기 때문에 속도가 향상 되었다고 볼 수 있다.
반대로 lazy loading은 실행 시점에 연산을 시작하기 때문에 성능지연 으로 볼 수 있을 것이다.
즉 코드 실행과 메모리 사용에 대한 비용을 계산하여 적절하게 lazy keyword 를 사용해야 한다.
간단한 연산 또는 효율적인 알고리즘을 반영한 코드를 구현하여 실행속도를 빠르게 만드는 것이 더 효율적일 수 있으며,
대용량 처리와 같이 필요시에만 연산이 시작될 수 있게 메모리적인 관점에서 접근하는 것이 더 효율적일 수 있는 것이다.
1) 주의 사항
1. lazy keyword는 한번만 초기화 된다는 것을 보장하지 않는다.
lazy loading 은 지연 초기화이지, 한 번만 초기화 한다는 의미가 아니다.
만일 멀티 스레드 환경에서 lazy loading으로 구현된 프로퍼티에 동시에 접근 할 경우 오히려 여러 번 초기화 될 수 있다.
또 스레드 안정성(Thread safety)을 보장하지 않기 때문에 스레드간 충돌이 일어날 수 있다.
2. 메모리 사용에 대한 부작용이 발생할 수 있다.
동시에 많은 양을 지연 초기화 하게 되면, 오히려 실행 시 순간 메모리 사용량이 증가 할 수 있다.
이로 인해 메모리 부족 문제가 발생 할 수 있으므로 메모리 사용량에 대한 고민이 필요하다.
4. 예시 코드
import Foundation
// lazy를 사용하지 않은 경우
struct WithoutLazy {
var value: Int = {
print("WithoutLazy 초기화 중")
return 1
}()
init() {
print("WithoutLazy 인스턴스 생성")
}
}
// lazy를 사용한 경우
struct WithLazy {
lazy var value: Int = {
print("WithLazy 초기화 중")
return 1
}()
init() {
print("WithLazy 인스턴스 생성")
}
}
// 인스턴스 생성
let withoutLazy = WithoutLazy()
var withLazy = WithLazy()
// 속성 접근
print("\nWithoutLazy의 value 접근")
print(withoutLazy.value)
print("\nWithLazy의 value 접근")
print(withLazy.value)
위의 코드를 실행하면 아래와 같은 결과가 나온다.
//클로저로 사용으로 초기화 실행
//WithoutLazy는 인스턴스 생성과 동시에 초기화
WithoutLazy 초기화 중
WithoutLazy 인스턴스 생성
//WithLazy는 인스턴스만 생성
WithLazy 인스턴스 생성
WithoutLazy의 value 접근
1
//value에 접근하자, 초기화 되는 것을 볼 수 있음
WithLazy의 value 접근
WithLazy 초기화 중
1
초기값 설정에 클로저를 사용하는 경우 프로퍼티 초기화 중에 실행된다.
WithoutLazy 는 인스턴스 생성과 동시에 초기화 되는것 을 볼 수 있다.
WithLazy는 인스턴스를 생성하고, value에 접근할 때 초기화 되는 것을 볼 수 있다.