[Swift] property wrapper

·2024년 7월 1일
0

Swift 문법

목록 보기
8/16

@State, @StateObject, @AppStorage… 등
SwiftUI를 공부했다면 익숙할 듯

위에 언급된 애들은 전부 property wrapper


요 글에선 얘네가 정확히 뭐고, 어떤 역할을 하는지에 대해 알아보려고 한다.
일단 이름 그대로 프로퍼티를 감싸는 것이라고도 생각할 수 있을 것 같다.

공식 문서에 의하면,

  • 프로퍼티가 저장되는 방법을 관리하는 코드 / 프로퍼티를 정의하는 코드 사이를 분리하는 계층을 추가
  • property wrapper를 정의할 때 관리(연산) 코드를 한번만 작성
    • 필요할 때 property wrapper로 감싸진 프로퍼티를 호출

→ 관리 코드를 한번만 작성하고 여러 프로퍼티에 적용할 수 있으므로 재사용성이 아~주 높아진다.


선언

property wrapper 정의

@propertyWrapper
struct SampleStruct {
}
  • @propertyWrapper
  • wrappedValue 프로퍼티 정의
  • struct, class, enum 모두 가능

예시

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }
}

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}
  • SmallNumberwrappedValue
    • number을 return
    • newValue와 maximum을 비교하여 작은 것을 number에 할당
  • ZeroRectangleheight, width 모두 SmallNumber 의 로직처럼 maximum을 넘는지 검사하고 싶을 때, property wrapper로 정의해 두지 않았다면 중복되는 로직의 코드를 2번 작성해 줘야 함
    → 재사용성 ♻️!

초기값 설정

위 코드로 계속 진행해 보겠슴

https://zeddios.tistory.com/1221
요기는 제드님 블로그를 보고 이해했습니다 감사합니다(—)(__)

나는 구조체니까 당연히 멤버와이즈로 초기화할 수 있을 테고, init이 따로 필요없을 거라 생각해서

let zeroRectangle = ZeroRectangle(height: 3, width: 3)

이렇게 선언해 주었더니

인스턴스를 생성하려고 할 때 이렇게 떴었음.

init(wrappedValue: Int) {
    maximum = 12
    number = min(wrappedValue, maximum)
}

wrappedValue를 초기화해 주는 이니셜라이저를 이렇게 넣었더니 Int 형으로 잘 나옴.

물론 maximum 프로퍼티도 초기화해줄 수 있고
init(), init(wrappedValue:maximum:)도 모두 가능함.

init(wrappedValue: Int, maximum: Int) {
    self.maximum = maximum
    number = min(wrappedValue, maximum)
}
  1. init()
init() {
    maximum = 12
    number = 3
}

let zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width) // 3 3

  1. init(wrappedValue: Int)
init(wrappedValue: Int) {
    maximum = 12
    number = min(wrappedValue, maximum)
}

struct ZeroRectangle {
    @SmallNumber var height: Int = 3
    @SmallNumber var width: Int
}

let zeroRectangle = ZeroRectangle(width: 15)
print(zeroRectangle.height, zeroRectangle.width) // 3 12
  • init(wrappedValue:)에 3 전달

  1. init(wrappedValue:maximum:)
init(wrappedValue: Int, maximum: Int) {
    self.maximum = maximum
    number = min(wrappedValue, maximum)
}

let zeroRectangle = ZeroRectangle(height: .init(wrappedValue: 3, maximum: 2), 
																	width: .init(wrappedValue: 12, maximum: 15))
print(zeroRectangle.height, zeroRectangle.width) // 2 12

이런 식으로 초기화가 가능하긴 하지만,
보통 쓰는 건 property wrapper에 인수를 포함하여 초기값을 설정하는 것


@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(wrappedValue: 3) var height: Int // 📌
    @SmallNumber(wrappedValue: 5, maximum: 13) var width: Int // 📌
}

projected Value

  • 프로퍼티 래퍼를 사용할 때 추가적인 정보나 기능을 제공하고 싶을 때 이용
  • $를 사용하여 접근
@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"

요거는 공식 문서에 적혀 있었던 예시인데
someNumber 이 maximum을 넘어서 조정되었는지 아닌지 추가적인 정보를 제공해 주는 projectedValue


뭔가 projectedValue라는 게 왜 꼭 굳이 필요하지? 라는 고민을 계속했었는데…
조금 더 기초적으로 접근해 보자면…

위의 코드에서 추가로projectedValue가 아니라 일반적인 isOver 뭐 이런 변수로 지정했을 때,

// property wrapper 정의하는 부분
private(set) isOver: Bool

var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                isOver = true
            } else {
                number = newValue
                isOver = false
            }
        }
    }

// someStructure 선언 이후
print(someStructure.isOver) // error

애초에 someNumber에서 isOver 같은 프로퍼티는 접근할 수가 없으니,
특별하게 추가적인 정보를 제공할 수 있도록 만들어진 projectedValue로 이리저리 필요한 로직을 구현하는 것

$를 붙여서 불러오기만 하면 이 프로퍼티에 대해서 지정해 놓은 정보를 다양하게 가져올 수 있게 된다.


특히 스유를 공부하면서 정말 많은 property wrapper를 사용했는데,
내가 커스텀해서 사용할 수 있다는 게 무척이나 신기했다!



Reference

https://bbiguduk.gitbook.io/swift/language-guide-1/properties
https://zeddios.tistory.com/1221

0개의 댓글

관련 채용 정보