티스토리 뷰

Swift

Property Wrapper

Zedd0202 2021. 3. 7. 18:35
반응형

 

안녕하세요 :) Zedd입니다.

오늘은 Property Wrapper에 대해서 공부해보겠습미당

 

# Property Wrapper

Swift 5.1에서 추가된 기능입니다.

직역하면 프로퍼티를 감싸는 그런 느낌인데..프로퍼티를 감싼다는게 어떤 의미일까요.

차근차근 알아보겠습니다.

 

# 들어가기 전에

SwiftUI를 공부하신 분들이라면

@Published

@Binding

@ObservedObject

@State

뭐 이런친구들을 보셨을텐데요.

@State를 예로 들면..

@State private var isPlaying: Bool = false

이렇게 사용할 수 있을겁니다.

 

이런것들이 Property Wrapper에요!

아주 살짝 익숙해졌으면 다행입니다..

 

# 필요성

일단 이 Property Wrapper를 사용해서 어떤 걸 할 수 있는지 보면 좋을 것 같아요. 

핵심 로직은 uppercased()입니다. 

제가 town을 꺼내와서 직접 uppercased()할 수 있지만..get할 때 uppercased()해주고 싶다 이겁니다.

Person이라는 타입 역시 name을 받고, 그걸 uppercased()해줍니다.

자..그럼 지금 로직이 중복되는 곳이 보입니다.

바로 get/set부분이죠.

get/set안에서 일종의 로직을 수행하는 것은 일반적이죠?

Swift 5.1에서 소개된 Property Wrapper는

이런 로직들을 프로퍼티 자체에 연결할 수 있어 보일러플레이트 코드와 코드 재사용성을 높혀줍니다. 

 

그럼 어떻게 사용하는지 보겠습니다.

 

# 정의 

Property Wrapper라는 이름에 걸맞게 프로퍼티를 가질 수 있는 타입 앞에 붙힐 수 있는데요. (class, struct, enum)

이렇게 타입 앞에 

@propertyWrapper를 붙혀 컴파일러에게 "이 타입은 특별하다"고 알려줍니다.

제가 Address, Person이라는 타입을 선언해줬었는데,

이 Address, Person이라는 타입 앞에 @propertyWrapper를 붙히는게 아니라,

내가 어떤 프로퍼티에 하고싶은 행동...이라고 해야할까요..!?

그 행동을 정의하는 타입을 하나 만든다고 생각하시면 됩니다.

Property Wrapper니까요! 

저는 Uppercased를 해주고싶으니 Uppercase라는 타입을 만든겁니다.

 

그냥 앞에 붙힌다고 해서 특별해지는 것은 아니고, 뭔가를 또 해줘야 합니다.

오류를 보면 wrappedValue가 없어 나는 오류 같습니다.

요구되는 wrappedValue라는 프로퍼티에 우리의 반복되는 로직을 넣으면 됩니다.

이 타입은 프로퍼티를 Uppercase로 만들어주도록 하는 Wrapper이기 때문에 

위 Address, Person에서 해준것처럼 get/set을 지정해줍니다.

초기값도 지정해줍니다.

 

# 사용법

위에서 언급했듯이 @State는 Property Wrapper였고, 

@State private var isPlaying: Bool = false

이렇게 사용했었습니다. 

똑같아요! 

struct Address {

    @Uppercase var town: String
}

이렇게 선언해주면 됩니다.

Property Wrapper는 반복되는 로직들을 프로퍼티 자체에 연결할 수 있다. 

즉 town이라는 프로퍼티 자체에 로직들을 연결할 수 있게 된겁니다.

struct Address {

    @Uppercase var town: String
}

let address = Address(town: "earth")
print(address.town) // EARTH

대문자로 잘 나오는 것을 볼 수 있죠.

 

Q : init이 있는 이유?

init(wrappedValue initialValue: String) {
	self.wrappedValue = initialValue
}

Property Wrapper쪽을 확인하면 이런 init이 있습니다.

만약 위 init이 없으면

이렇게 되면 Address의 memberwise initializer가 String타입으로 만들어지는게 아니라

Uppercase타입으로 만들어지더라구요..? 

현재 town은 Uppercase라는 Property Wrapper에 연결되어있고..

지금 town이라는 프로퍼를 wrap한거니까...;; Uppercase쪽으로 연결?? 되는 것 같은데..

이걸 논리정연하게 설명을 못하겠네요; 

 

Uppercase역시 하나의 struct로 만들어져있기 때문에 

struct Address {
    
    @Uppercase(wrappedValue: "earth")
    var town: String
}

이렇게 Uppercase자체에서 init을 호출 가능합니다. (지금은 내가 만들어준 init을 호출했음) 

struct Address {
    
    @Uppercase(wrappedValue: "earth")
    var town: String
}

let address = Address()
print(address.town) // EARTH

그래서 이게 가능해지는...

 

근데 지금 같은 경우에는 town에 기본값을 할당하는것이 적절하지 않으므로 

struct Address {
    
    @Uppercase var town: String
}

let address = Address(town: "earth")
print(address.town) // EARTH

그냥 이렇게 해줍니다.

 

연산 프로퍼티는 자기자신의 값을 바꿀 수 없기 때문에..

private저장 프로퍼티를 하나 두고 그 프로퍼티를 가지고 연산을 했는데요.

그게 싫다면

didSet을 이용하는 방법도 있을 것 같습니다.

⚠️ didSet은 init시에는 호출되지 않으므로 init에서도 uppercased를 호출해줘야 합니다.

 

# 활용법

이해를 돕기위해 쉬운 예제를 사용해봤는데요.

Property Wrapper를 활용한 iOS개발에 도움이 될만한 것들은 어떤게 있을까요? 

WWDC 19에서도 사용한 UserDefaults예제가 가장 적절할 것 같습니다.

역시나 get/set부분이 중복되고 있습니다. 

다른건 key와 타입정도네요.

이걸 Property Wrapper를 이용해서 개선해보겠습니다.

이렇게 선언해주고, 

이렇게 사용하면 됩니다.

위에서 봤던 예제는 기본값을 할당하기에는 적절하지 않았지만, 지금은 우리가 만들어준 UserDefaults Property Wrapper의 이니셜라이저를 이용하여 기본값을 지정해줍니다.

기존에 있었던 get/set 같은 보일러 플레이트 코드들 없이 동일한 효과를 낼 수 있게 되었습니다.

위 코드는 WWDC19 What's New in Swift에서 나온 코드이지만, 위 코드를 조금 더 개선한다면, 

storage역시 지정할 수 있도록 하면 좋을 것 같네요!

물론 standard만 써도 되는 상황이라면

UserDefault Property Wrapper쪽에 initializer를 따로 놓고 기본값을 넣어주도록 해도 될 것 같습니다. 

이렇게요!

 

 

참고

www.swiftbysundell.com/articles/property-wrappers-in-swift/

developer.apple.com/wwdc19/402

 

반응형

'Swift' 카테고리의 다른 글

dynamicCallable  (0) 2021.03.18
dynamicMemberLookup  (0) 2021.03.11
Key-Value Observing(KVO) in Swift  (2) 2021.03.01
Key-Value Coding(KVC) / KeyPath in Swift  (4) 2021.02.28
Unowned Optional References  (11) 2021.02.22