구조체와 클래스에서 프로퍼티의 저장은 각각 다르게 적용된다.
하나씩 살펴보자
📌 저장된 프로퍼티(Stored Properties)
var (변수) 혹은 let(상수)를 통해 저장된 프로퍼티를 쓸 수 있다.
1️⃣ 구조체에서의 저장 프로퍼티
첫번째 요소와 길이 범위를 설정하는 프로퍼티를 갖고 있는 FixedLengthRange 구조체가 있다.
FixedLengthRange 의 인스턴스는
1. firstValue 라는 저장된 프로퍼티 변수가 있으며
2. length 라는 저장된 프로퍼티 상수를 가진다.
length 는 새 범위가 생성될 때 초기화되며 프로퍼티 상수 이므로 변경할 수 없다.
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
생성한 구조체를 사용해 보자.
구조체 초기화 구문을 통해 설정한 값들을 rangeOfItems 변수에 할당한 다음,
firstValue 프로퍼티를 6으로 변경하였다.
var rangeOfItems = FixedLengthRange(firstValue: 0, length: 3)
//0,1, and 2
rangeOfItems.firstValue = 6
// 6, 7, and 8
만약에 상수에 구조체를 할당하면 ?!
구조체의 프로퍼티가 변수로 선언되어 있어도, 인스턴스의 프로퍼티를 변경할 수 없다.
let rangeOfItems = FixedLengthRange(firstValue: 0, length: 4)
// 상수로 선언한 뒤, 인스턴스 생성
rangeItems.firstValue = 6
// rangeItems는 상수이므로 프로퍼티 수정 불가
왜 그럴까🤔??
👉 구조체는 값 타입이기 때문이다.
값 타입의 인스턴스는 상수로 선언되면, 모든 프로퍼티에 대해 수정이 불가능하다.
( let notChange = FixedLengthRange() -> notChange의 프로퍼티 수정 불가)
✅ 하지만 클래스는 참조 타입이라 다르게 동작한다.
클래스는 참조 타입의 인스턴스를 상수로 선언해도, 인스턴스의 프로퍼티 변수에 대해 변경이 가능하다.
2️⃣ 늦은 저장 프로퍼티(Lazy Stored Properties)
- 프로퍼티 선언 전에 lazy 수정자를 붙인다.
- lazy가 붙은 프로퍼티는 처음 사용될 때 까지, 그 초기값이 계산되지 않는다.
- 외보 요인에 의해 초기값이 달라질 때 or 초기값이 필요할 때까지 수행되면 안되는 작업이 있을 때 사용하면 유리
주의📢
lazy를 사용할 때는 프로퍼티를 var 키워드를 사용하여 변수로 선언해야 함
➡️ 인스턴스 초기화가 완료된 이후에도 초기값이 없을 수 있기 때문이다. (상수는 초기화가 완료되기 전에 항상 값을 갖고 있어야 함 👉 즉, lazy로 선언할 수 없음)
class DataImporter {
var filename = "data.txt"
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
3️⃣ 계산된 프로퍼티 (Computed Properties)
값을 실질적으로 저장하는 것이 아니라, 다른 프로퍼티와 값을 간접적으로 조회하고 설정하는 getter와 setter를 제공함
아래 예시에서 Point와 Size는 값을 저장하는 프로퍼티들을 갖고 있지만,
Rect 구조체는 center라는 계산된 프로퍼티를 가진다. Rect이 반환하는 x와 y 값의 포인트는 명시적인 값으로
저장되는 것이 아니라, origin과 size로 계산되어 결정된다.
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
해당 구조체를 사용해보자
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
// 사각형 중앙의 값은 (5.0, 5.0)
square라는 변수를 통해 새로운 Rect 변수를 생성하고 원점은 (0,0)으로 너비는 (10,10)으로 설정한다
👉 그렇다면 square.center 결과값은??
center는 getter를 호출하여 현재 프로퍼티 값을 조회한 다음( (0,0) , (10,10)) 사각형의 중심 값을 계산한다.
그리고 Point 리턴값인 (5,5)를 결과로 반환한다.
아예 center 프로퍼티의 값을 새롭게 설정했다면 어떻게 될까
중심점을 (15.0, 15.0)으로 설정하면 center의 setter가 호출되어 origin 프로퍼티에 저장된 x값과 y값을 변경하고
변경된 중점으로 사각형을 위치시킨다.
square.center = Point(x: 15.0, y: 15.0)
print("square.origin 값은 x는 \(square.origin.x) y는 \(square.origin.y)")
// x는 10.0 y는 10.0
4️⃣ 짧은 Setter 선언
setter 설정값 이름을 정의하지 않았다면, newValue 라는 기본 이름이 사용된다.
struct AlternativeRect {
...
var center: Point {
...
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
5️⃣ 짧은 Getter 선언
기존 getter에서 return이 생략되었으며, 계산 식을 x, y 안에 포함한다.
struct CompactRect {
...
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
...
}
}
6️⃣ 읽기 전용 계산된 프로퍼티
setter가 없고 getter 만 있는 것을 읽기 전용 계산 프로퍼티(아래 Cuboid 구조체에서 volume 프로퍼티에 해당)라 한다.
- 항상 값을 반환함
- 점 구문으로 접근 가능
- 다른 값은 설정 불가 (읽기 전용)
- get 키워드와 중괄호를 삭제하여 간편하게 선언 가능
주의📢
구조체 안 프로퍼티는 상수로 선언하면 안된다. 값이 고정되어 있지 않기 때문에, var 변수로 선언해야 하며
인스턴스 초기화 부분에서 한 번 설정 시 값이 변경될 수 없으므로 이 때 let 키워드를 사용한다.
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
값 접근 방법 -> 점 구문 사용
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("fourByFiveByTwo 의 부피는 \(fourByFiveByTwo.volume)")
// Prints fourByFiveByTwo 의 부피는 40.0
7️⃣ 프로퍼티 관찰자(Property Observers)
- 프로퍼티의 값이 변경되는 지 관찰하고 응답함
- 새로운 값이 현재 값과 같더라도 호출
- 상속에서 하위 클래스의 프로퍼티를 재정의하여 프로퍼티 관찰자를 추가함
프로퍼티 관찰자 정의 방법
willSet | didSet | |
특징 | - 값이 저장되기 직전에 호출 - 새로운 프로퍼티 값이 전달됨 |
- 새로운 값이 저장되자 마자 호출 - 예전 프로퍼티 값을 포함한 상수 파라미터가 전달됨 |
willSet과 didSet의 예시를 살펴보자
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("totalSteps는 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("\(totalSteps - oldValue) 만큼의 값이 증가")
}
}
}
}
willSet은 새로운 값을 위한 newTotalSteps 파라미터 명을 사용한다.
didSet은 totalSteps 값이 업데이트 되고 난 후에 호출된다.
오래된 값에 대해 oldValue 기본 이름을 사용하고, 새로운 값 totalSteps와 비교한다.
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// totalSteps는 200
// 200 만큼의 값이 증가
stepCounter.totalSteps = 360
// totalSteps는 360
// 160 만큼의 값이 증가
stepCounter.totalSteps = 896
// totalSteps는 896
// 536 만큼의 값이 증가
8️⃣ 프로퍼티 래퍼(Property Wrappers)
프로퍼티가 저장되는 방법을 관리하는 코드와 프로퍼티를 정의하는 코드 사이에 분리 계층을 추가한다.
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
TwelveOrLess 구조체는 래핑하는 값이 항상 12와 같거나 더 작은 숫자만을 가지도록 설정된 구조체이다.
setter를 통해 12보다 작은 값을 설정하고, 설정된 값을 getter를 통해 반환한다.
이렇게 설정한 프로퍼티 래퍼는 속성으로 사용가능하며, 프로퍼티 전에 래퍼의 이름을 작성하여 적용하면 된다.
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
height와 width 프로퍼티는 항상 12이하의 값을 가지는 지 TwelveOrLess 프로퍼티 래퍼를 통해 확인한다.
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
rectangle의 높이(rectangle.height)의 초기값은 number = 0 으로 초기화 되어 있기 때문에 0이다.
이때, .height을 10으로 저장하면 12보다 작기 때문에 성공적으로 저장되지만,
24를 저장하면 12보다 크기 때문에 실질적으로 12가 저장된다.
다르게 프로퍼티 래퍼 사용하는 방법
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
9️⃣ 프로퍼티 래퍼 초기값 설정하기
위 프로퍼티 래퍼는 초기값 지정이 불가능하다.
초기값을 설정하기 위해서는 다음과 같이 init() 을 통해 정의할 필요가 있다.
아래의 SmallNumber 구조체는
init(), init(wrappedValue:), init(wrappedValue:maximum:) 의 3개의 초기화를 가지고 있다.
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
이를 사용해보자.
1. init() 사용하기
기존의 프로퍼티 래퍼를 사용하는 방법과 동일하게 프로퍼티 변수 앞에 선언해주면 된다.
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// 0 0
2. init(wrappedValue:) 사용하기
height와 width 값을 원하는 값으로 저장해주면 된다.
height 와 width 에 1을 저장해주면 SmallNumber의 인스턴스가 SmallNumber(wrappedValue: 1) 호출로 생성된다.
이로인해 wrappedValue 값으로 1을 사용하고 기본 최대값으로 12를 사용한다.
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 1 1
3. init(wrappedValue:maximum:) 사용하기
init(wrappedValue:maximum:) 형태에 smallNumber 래퍼를 씌워 wrappedValue와 maximum 값을 설정하면 된다.
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// 2 3
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// 5 4
4. 각 프로퍼티에 서로 다른 초기화 적용하기
height에는 init(wrappedValue:) 초기화를 호출하고, width에는 init(wrappedValue:maximum:)를 호출해보자
width를 래핑한 인스턴스는 SmallNumber(wrappedValue: 2, maximum: 9) 를 호출한다.
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// Prints "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// Prints "12"
'IOS > Swift' 카테고리의 다른 글
[Swift] 서브 스크립트(Subscripts) (0) | 2023.02.13 |
---|---|
[Swift] 메서드(Methods) (0) | 2023.02.13 |
[Swift] 구조체와 클래스(Structures and Classes) (0) | 2023.02.08 |
[Swift] 열거형(Enumerations) (0) | 2023.01.16 |
[Swift] 제어 변경 구문과 guard 구문 (0) | 2022.12.29 |