← 메인으로

0805-1 저장 프로퍼티, 연산 프로퍼티

[!note] 주말에 공부했던 부분이지만 한 번에 설명하기엔 머리에 들어오지 않아서, 다시 한 번 정리해보자.

스위프트의 클래스 프로퍼티는 저장 프로퍼티, 연산 프로퍼티로 나뉜다. 저장 프로퍼티는 상수, 변수에 담기는 값이다.

반면 연산 프로퍼티는 프로퍼티에 값을 설정하거나 가져오는 시점에서 어떤 계산이나 로직에 따라 처리된 값이다. 연산 프로퍼티는 게터를 생성하고, 선택적으로 세터 메서드를 생성하며, 연산을 수행할 코드가 포함된다. 예를 들어, BankAccount 클래스에 은행 수수료를 뺀 현재 잔액을 담는 프로퍼티가 추가로 필요하다고 해보자. 저장 프로퍼티를 이용하는 대신 값에 대한 요청이 있을 때마다 계산되는 연산 프로퍼티를 이용하는 게 더 좋겠다.

class BankAccount { var accountBalnce: Float = 0 var accountNumber: Int = 0 let fees: Float = 25.00 var balanceLessFees: Float // 이게 연산 프로퍼티. get { return accountBalance - fees // 이게 게터 메소드. } } init(number: Int, balance: Float) { accountNumber = number // 여기서 할당을 해주고, 후에 number에 숫자를 부여. accountBalance = balance } } 
var balanceLessFees: Float { get { return accountBalance - fees } set(newBalance) { accountBalance = newBalance - fees } } 
var balance1 = account1.balanceLessFees account1.balanceLessFees = 12123.12 

지연 저장 프로퍼티

프로퍼티를 초기화하는 여러 방법이 있는데, 가장 기본적인 방법은 다음과 같이 직접 할당하는 것이다.

var myProperty = 10 
class MyClass { let title: String init(title: String) { self.title = title } } 
class MyClass { var myProperty: String = { var result = resourceIntensiveTask() result = processData(data: result) return result }() } 

[!note] 잠깐만, 클로저는 뭐지? 조금 더 복잡하다면서 이건 아예 뭔 소린지 모르겠는데ㅋㅋㅋㅋㅋㅋㅋ [[0805-2 클로저]]

복잡한 클로저의 경우는 초기화 작업이 리소스와 시간을 많이 사용하게 될 수 있다. 클로저를 이용하여 선언하면 해당 프로퍼티가 코드 내에서 실제로 사용되는지와는 상관없이 클래스의 인스턴스가 생성될 때마다 초기화 작업이 수행될 것이다.

예를 들어, 데이터베이스로부터 데이터를 가져오거나 사용자로부터 사용자 입력을 얻게 될 때, 실행 프로세스의 후반부 단계까지 프로퍼티에 값이 할당되었는지 모르게 되는 상황이 생길 수 있다. 이러한 상황에서의 훨씬 더 효율적인 방법은, 프로퍼티를 최초로 접근할 때만 초기화 작업을 하는 것이다. 다행히도 이 작업은 다음과 같이 lazy로 프로퍼티를 선언하면 된다.

class MyClass { lazy var myProperty: String = { var result = resourceIntensiveTask() ressult = processData(data: result) return result }() } 

느낌은 온다, 하지만 이해가 잘 가지 않는다. 코드를 한 번만 더 뜯어보자.

class MyClass { var myProperty: String = { var result = resourceIntensiveTask() result = processData(data: result) return result }() } 
{ var result = resourceIntensiveTask() result = processData(data: result) return result } 
let tmpClosure = { return "Hello" } let result = tmpClosure() // "Hello" 
let result = { return "Hello" }() 

결론적으로 이건 뭐냐?

class MyClass { var myProperty: String = { // 클로저(익명 함수) var result = resourceIntensiveTask() result = processData(data: result) return result }() // 이 클로저를 즉시 실행하고, 결과값을 myProperty에 저장! } 

[!note] 거의 이해가 잘 된다. 다만 한가지 짚고 가야 할 게 보인다. 스위프트 처음 공부하면서부터 헷갈렸던 건데, 도대체 result가 몇 번을 똑같은 게 나오냐는거다. GPT한테 상담을 요청했다.

먼저 구조만 보자

var result = resourceIntensiveTask() result = processData(data: result) return result 
  1. var result = resourceIntensiveTask()
    • resourceIntensiveTask()는 무거운 작업을 하는 임의의 함수다. (파일 읽기, API 호출 등)
    • 이 함수가 리턴한 값을 result에 담는다.
    var result = "RawData" 
 2. result = processData(dat: result) - 아까 리턴된 값인 result를 processData 함수에 넣는다. - 이 함수가 리턴된 값을 같은 이름의 return 변수에 **덮어씌운다** ```swift result = processData(data: "RawData") 
  1. return result
    • 마지막으로 그 가공된 데이터를 반환한다.

왜 헷갈렸나?

마지막으로 한 개만 더 점검하자.

lazy를 사용하면 프로퍼티가 최초로 접근될 때만 초기화된다는 말의 의미를 깊이 이해하고 싶다.

class MyClass { lazy var text: String = { print("클로저 실행됨!") return "Hello World" }() } let obj = MyClass() // 아직 아무 일도 안 일어남. print(obj.text) // 이 순간 "클로저 실행됨!" 출력, 값이 할당됨. print(obj.text) // 이번엔 실행되지 않음. 이미 초기화된 값이기 때문. 
클로저 실행됨! Hello World Hello World 
  1. 인스턴스를 만든다고 값이 만들어지는 게 아니다.
    • MyClass() 했을 때, text 프로퍼티에는 아무 일도 일어나지 않는다.
    • lazy가 붙은 프로퍼티는 초기화를 미룬다.
  2. 최초 '접근'이란?
    • obj.text를 읽거나, 출력하거나, 조건문에 쓰거나, 값을 변경하려 할 때
    • 이때 내부적으로 클로저가 한 번만 실행되어 값이 설정된다.
  3. 한 번만 실행된다.
    • 클로저는 딱 한 번만 실행된다.
    • 이후에는 이미 저장된 값이 계속 재사용된다.

실제 사용 시나리오

  1. 리소스 낭비 방지
    lazy var image = loadHighResolutionImage() // 접근하지 않으면 이미지도 로딩 안됨 -> 메모리 아낌. 
 2. 상황 따라 값 바뀌게 만들 수 있음 ```swift var usernameInput: String = "" lazy var greeting: String = { return "Welcome, \(usernameInput)" }() 

메모리적 관점

요약

lazy는 리소스를 아끼고, 필요한 순간에만 계산을 수행하는 똑똑한 프로퍼티 초기화 방식이다. 한 번만 실행되고, 그 결과는 이후에도 계속 재사용된다.