클래스(class)와 구조체(struct)
속성(Property)과 메서드(Method)
속성은 구조체와 클래스가 거의 차이가 없음
메서드에서 차이가 발생
구조체, 클래스의 속성
저장속성(Stored Properties) / 지연 저장 속성
저장속성(Stored Properties)
값이 저장되는 일반적인 속성(변수)을 저장 속성이라고 함
저장속성은 구조체, 클래스 동일
let(상수) 또는 var(변수)로 선언 가능 (만약 저장 속성을 let으로 선언하면 값을 바꿀 수 없음)
저장 속성(변수)은 각 속성자체가 고유의 메모리 공간을 가짐
초기화 이전에 값을 가지고 있거나, 생성자 메서드를 통해 값을 반드시 초기화 해야만 함
지연 저장 속성(Lazy Stored Properties)
struct Bird1 {
var name: String
lazy var weight: Double = 0.2 // 처음부터 메모리 공간을 만들지는 않음
init(name: String) {
self.name = name
}
func fly() {
print("날아갑니다.")
}
}
var aBird1 = Bird1(name: "새") // weight 속성 초기화 안됨
aBird1.weight // 해당 변수에 접근하는 이 시점에 초기화 됨(메모리 공간이 생기고 숫자가 저장됨)
지연 저장 속성은 "해당 저장 속성"의 초기화를 지연시키는 것
인스턴스가 초기화되는 시점에 해당 속성이 값을 갖고 초기화되는 것이 아니라(메모리에 공간과 값을 갖는 것이 아니라),
해당 속성(변수)에 접근하는 순간에 (해당 속성만)개별적으로 초기화됨
따라서, 상수로의 선언은 안되고 변수(var)로의 선언만 가능
lazy var만 가능(lazy let 불가능)
지연 저장 속성은 "선언시점에 기본값을 저장"해야함
생성자에서 초기화를 시키지 않기 때문에 "선언시점에 기본값을 저장"해야함
지연(lazy) 저장 속성 -> 구조체, 클래스 동일
값을 넣거나, 표현식(함수 실행문)을 넣을 수 있음(모든 형태의 표현식)
함수호출 코드, 계산코드, 클로저 코드 등 모두 가능 -> 저장하려는 속성과 "리턴형"만 일치하면 됨
지연 저장 속성으로 선언된 "해당 속성"의 초기화 지연 -> 메모리 공간이 없다가 처음 접근하는 순간에 (해당 속성만)개별적으로 초기화됨
지연 저장 속성을 사용하는 이유
1) 메모리를 많이 차지할 때
2) 다른 속성을 이용해야 할 때
계산속성(Computed Properties)
class Person {
var birth: Int = 0
var age: Int { // 타입을 설정해줘야 함
get {
return 2021 - birth
}
set(age) {
self.birth = 2021 - age
}
// set(newValue) { // 문법적인 약속으로 newValue 가능 파라미터 없어도 가능
// self.birth = 2021 - newValue
}
}
var p1 = Person()
p1.birth = 2000
p1.age // get
p1.age = 20 // set 세팅해줌
p1.birth
set 블럭에서 newValue는 파라미터라고 생각
get블럭은 반드시 구현해야하고 set블럭은 생략 가능
get블럭만 구현할 때는 get삭제가능하지만 줄맞춤을 잘 해줘야 함
밖에서 해당 인스턴스에 접근해서 "get" ➞ 값을 얻는다는 의미
밖에서 해당 인스턴스에 접근해서 "set" ➞ 값을 세팅(설정)한다는 의미
메서드가 아닌 속성방식으로 구현했을때의 장점
- 관련이 있는 두가지 메서드(함수)를 한번에 구현할 수 있다.
- 외부에서 보기에 속성이름으로 설정가능하므로 보다 명확해 보인다.
- 따라서, 계산속성은 메서드를 개발자들이 보다 읽기 쉽고, 명확하게 쓸 수 있는 형태인 속성으로 변환해 놓은 것이다.
- 실제로, 계산 속성은 겉모습은 속성(변수)형태를 가진 메서드(함수)임
- 계산 속성은 실제 메모리 공간을 가지지 않고, 해당 속성에 접근했을 때 다른 속성에 접근해서 계산한 수 그 계산 결과를 리턴하거나 세팅하는 메서드이다.
주의점
계산 속성 -> 구조체, 클래스, (열거형) 동일
- 항상 변하는 값이므로, var로 선언해야 함 (let 선언 불가)
- 자료형 선언을 해야함(형식추론 형태 안됨) (메서드이기 때문에 파라미터, 리턴형이 필요한 개념)
- get은 반드시 선언해야함 (값을 얻는 것은 필수, 값을 set하는 것은 선택)
타입 속성(Type Properties) - 타입자체에 속한 속성
저장 속성, 계산 속성 2가지 모두 타입속성이 될 수 있음
저장 타입 속성
일반 저장속성은 인스턴스를 생성할 때, 생성자에게 모든 속성을 초기화 완료.
그리고, 해당 저장 속성은 각 인스턴스가 가진 고유한 값임
하지만, 저장 타입(형식) 속성은 생성자가 따로 없기때문에, 타입 자체(유형 그자체)에 속한 속성이기 때문에
항상 기본값이 필요. 생략할 수 없음
static let pi: Double = 3.14
static let pi: Double // 불가능. 기본값 필요 생략불가
지연 속성의 성격을 가짐
저장 타입속성은 기본적으로 지연 속성 (속성에 처음 접근하는 순간에 초기화됨)이지만, lazy라고 선언할 필요는 없음
(참고: 여러 스레드에서 동시에 액세스하는 경우에도 한 번만 초기화되도록 보장됨. Thread-Safe)
주의점
타입 속성(Type Properties) -> 클래스, 구조체, (열거형)에 모두 추가할 수 있음
let 또는 var 둘 다 선언 가능
타입 속성은 특정 인스턴스에 속한 속성이 아니기 때문에 인스턴스 이름으로는 접근 불가
인스턴스 내에서도 접근하려면 타입이름 + 속성으로 써야 접근 가능함
어떤 경우에 타입 속성을 선언해야 할까?
모든 인스턴스가 동일하게 가져야하는 속성이거나(해당 타입의 보편적인 속성),
모든 인스턴스가 공유해야하는 성격에 가까운 이어야 함
상속에서 재정의(overriding)
1) 저장 타입 속성 -> 상속에서, 하위클래스에서 재정의 불가능(class키워드 안됨)
// (인스턴스의 경우도 저장 속성은 고유의 틀이기 때문에 건드릴 수 없음)
2) 계산 타입 속성 -> 상속에서, 상위클래스에서 class키워드를 붙인 경우, 재정의 가능
class키워드(계산 타입 속성만)
상속이 있는 경우, 계산 타입 속성에서는 static대신 class키워드를 사용
(->static과 동일한 역할)하면 재정의 가능한 속성이 됨
속성 감시자(Property Observer)
쉽게 생각해서 저장속성을 감시하는 거라고 생각하기
class Profile {
// 일반 저장 속성
var name: String = "이름"
// 저장속성 + 저장 속성이 변하는 시점을 관찰하는 메서드
var statusMessage: String = "기본 상태메세지" {
willSet(message) {
print("메세지가 \(statusMessage)에서 \(message)로 변경될 예정입니다.")
}
}
}
let p = Profile()
p.name
p.name = "전지현" // 얘는 이름이 변하고 끝
p.statusMessage
p.statusMessage = "행복해" // 메세지가 기본 상태메세지에서 행복해로 변경될 예정입니다.
p.statusMessage = "우울해" // 메세지가 행복해에서 우울해로 변경될 예정입니다.
값이 바뀔 때마다 willSet 부분을 호출하는것을 볼 수 있음
기본적으로 "속성 감시자"라는 이름이지만, 성격은 메서드 -> 저장 속성 감시
저장 속성이 변하는 시점을 관찰
저장 속성이 변하는 순간 -> 딸려있는 메서드가 호출됨
willSet은 값이 저장되기 직전에 호출됨
속성감시자의 2가지 종류 - willSet / didSet
1) willSet은 값이 저장되기 직전에 호출
2) didSet은 새 값이 저장된 직후에 호출
클래스, 구조체, (열거형) 동일하게 적용
어떤 속성이 변하는 시점을 알아차리도록 시점에 제약을 만드는 코드를 짜기는 어려움
-> 그래서 실제 앱을 만들고 활용할 때 좋은 수단
일반적으로는 willSet 또는 didSet 중에서 한가지만 구현
(실제 프로젝트에서는 didSet을 많이 사용)
파라미터의 생략 - oldValue / newValue
class Profile {
// 일반 저장 속성
var name: String = "이름"
var statusMessage = "기본 상태메세지" {
willSet {
print("메세지가 \(statusMessage)에서 \(newValue)로 변경될 예정입니다.")
print("상태메세지 업데이트 준비")
}
didSet {
print("메세지가 \(oldValue)에서 \(statusMessage)로 이미 변경되었습니다.")
print("상태메세지 업데이트 완료")
}
}
}
속성 감시자가 왜 필요할까
변수 변하면, 변경 내용을 반영하고 싶을 때(업데이트) -> 실제프로젝트에서 쉽게 이해가 되는 부분
예) 상태메세지 변경
주의점
속성 감시자를 추가 가능한 경우
1) 저장 속성 (원래, 상속한 경우 둘 다 가능) - 일단 저장속성을 관찰한다고 생각하기
2) 계산 속성 (상속해서 재정의하는 경우에만 가능) (단순 메서드 추가) - 추가 할 수도 있긴하다.
계산 속성의 경우, 속성 감시자를 만드는 대신 계산 속성의 set블록에서 값 변경을 관찰할 수 있기 때문에
(재정의(상속)이 아닌 본래의 계산 속성에는 추가 불가)
let(상수) 속성에는 당연히 추가 안됨 (값이 변하지 않으므로, 관찰할 필요가 없기 때문에)
지연저장 속성에 안됨 (스위프트 5.3이후 업데이트: 지연 저장 속성에도 속성 감시자 구현 가능)
속성감시자의 동작의 메커니즘
실제 값의 변경이 일어나지 않아도, 값을 Set하는 작업을 하면 무조건 호출
'iOS 관련 공부' 카테고리의 다른 글
속성(Property)과 메서드(Method) - 2 (0) | 2025.02.24 |
---|---|
타입 캐스팅 (Type Casting) (0) | 2025.02.21 |
초기화 (initialization) - 1 (0) | 2025.02.13 |
클래스와 구조체 (0) | 2025.02.06 |
스토리보드 사용해서 앱 만들기 (0) | 2025.02.05 |