combine을 공부하면서 Published 의 구현부를 뜯어보는데
@propertyWrapper struct Published<Value>
@propertyWrapper 라는 것이 있는 것을 보았다.
많이 들어본 것 같았는데... 저게 뭘까?
@propertyWrapper 를 먼저 공부해 보자!
🎁 @property wrapper 란?
@property wrapper 공식문서가 language guide 에 있는게 조금은 충격이었다. language guide 열심히 읽었어서 다 아는 내용인줄 알았는데... 놓친게 있다 다시 한번 읽어야 할듯...ㅎ
공식문서에 씌여 있는 정의를 보자.
property wrapper는 프로퍼티가 저장되는 방법을 관리하는 코드와 프로퍼티를 정의하는 코드 사이에 분리 계층을 추가합니다.
이게 무슨 말이지... 싶다.
우선 여기서 알 수 있는 점은 저장과 관리를 코드를 따로 둔다! 라는 것이다.
SwiftUI 에서 @ObservedObject, @Published 들이 property wrapper 로 구현이 되어있다.
class UserInfo {
@Published var name: String
}
이런 UserInfo 라는 모델 클래스가 있다. 우리는 name 변수에 유저 이름이 저장되어있다.
name 변수의 값이 바뀌면, UserInfo 를 가지고 있는 View 가 없데이트 될 것이다.
그런데 name 에 set 을 하면 어떻게 된다 이런 로직이 어디에 있을까?
@propertyWrapper struct Published<Value>
Published 내부에 있다!
이래서 저장과 관리를 분리할 수 있다고 한 것이다.
아래의 예제를 보면 더 쉽게 이해할 수 있다.
🎁 @property wrapper 가 필요한 순간
SmallRectangle
이라는 구조체를 만들고width
와 height
의 값을 저장하는 프로퍼티를 만들었다.
그런데 SmallRectangle
이기 때문에 12 가 넘는 값을 설정하면 12로 설정 되도록 만들고 싶다.
struct SmallRectangle {
private var sizeHeight: Int
private var sizeWidth: Int
var height: Int {
get {
return self.sizeHeight
}
set(height) {
if height > 12 {
self.sizeHeight = 12
} else {
self.sizeHeight = height
}
}
}
var width: Int {
get {
return self.sizeWidth
}
set(width) {
if width > 12 {
self.sizeWidth = 12
} else {
self.sizeWidth = width
}
}
}
}
🤚짠🖐
height 와 width 부분에 겹치는 코드가 너무 많다.
만약에, SmallTryangle 이라는 구조체를 하나 더 만든다고 가정하면, 저 heigth 와 width 와 비슷한 연산 프로퍼티를 2개 더 만들어야 할 것이다.
프로퍼티 래퍼를 사용하면 이러한 12 이하의 값만 가지는
특성을 가지는 프로퍼티를 만들어 줄 수 있다.
🎁 @property wrapper 만들기
위에서 말한12 이하의 값만 가지는
특성을 가지는 프로퍼티를 공식문서에서 propertyWrapper 로 만들어 놓았다.
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
@propertyWrapper
키워드를 붙여준다.
그러면 wrappedValue
라는 변수를 만들어 주어야 한다.
먼저 private 으로 변수를 담아줄 number 프로퍼티를 설정하고 wrappedValue 라는 연산 프로퍼티를 이용하여 12 이하의 값을 가지는 number 프로퍼티를 설정해 준다.
이 TwelveOrLess 라는 구조체를 하나의 프로퍼티로 설정하려면 아래와 같이 해주면 된다.
구조체만 되는 것은 아니고
구조체, 열거형, 클래스도 된다.
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"
rectangle.height = 10
print(rectangle.height)
// Prints "10"
rectangle.height = 24
print(rectangle.height)
// Prints "12"
어디서 많이 본 모양인데, @State @ObservedObject 쓰는 것과 똑같다.
wrappedValue
에 지정된 타입을 가지는 프로퍼티 처럼 사용할 수 있다!
코드가 훨씬 깔끔해지고 간단해졌다🥳
@ 를 붙이지 않고,
아래와 같이 타입을 직접 생성해 주는 것도 가능하다.
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 }
}
}
🎁 @property wrapper의 init
@propertyWrapper 에도 초기 값을 설정 할 수 있다.
@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)
}
}
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// Prints "0 0"
init 에서 별 다를 것이 없다.
다른 클래스 구조체에 init 을 만들 듯이 init 을 만들어 주면 되는 것이다.
init(wrappedValue:)
라는 프로퍼티가 있는데
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
// 이렇게 기본값을 정해주게 되면
// init(wrappedValue:) 를 사용해서 초기화 하는 것이다
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// Prints "1 1"
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)
// Prints "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// Prints "5 4"
이런식으로 지정하는 것도 가능하다.
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"
이런식으로 wrappedValue
는 기본값으로 받을 수도 있다.
🎁 projectedValue
projectedValue
라는 프로퍼티를 추가하면 $ 을 사용하서 접근할 수 있다.
@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"
위의 코드에서는 12 이하의 값을 넣었을 때는 값이 바뀌지 않았다는 뜻으로 true 를 저장하고 값이 12로 저장되었을 때는 바뀌었다는 의미로 false 를 저장했다.
Bool 값 말고도 다양한 타입으로 projectedValue
를 설정할 수 있다.
생각보다 완전 좋은 기능인 것 같다.
@State @Published 들의 내부 구현을 더 잘 알게 된 거 같다!
'🍎 iOS > 🕊️ swift' 카테고리의 다른 글
[swift]Date DateFormatter CheetSheet (0) | 2023.01.10 |
---|---|
[swift] 의존성 주입으로 코드를 예쁘게 하자! (0) | 2023.01.06 |
[swift] 부동 소수점 오류가 나는 이유 (0) | 2023.01.06 |
[swift] 고차 함수 요목조목 보기 (0) | 2023.01.06 |
[iOS] Number Fomatter (0) | 2023.01.06 |