프로그래밍언어

[Swift] Property Wrapper: 개념, 사용법, 사용자 정의

애기공룡훈련병 2025. 2. 19. 12:25
반응형

Property Wrapper란?

Swift의 Property Wrapper는 프로퍼티에 특정 기능을 추가할 수 있도록 도와주는 기능입니다. 반복적으로 사용되는 로직을 추상화하여 코드의 중복을 줄이고, 가독성을 높이는 데 기여합니다. 예를 들어, UserDefaults에 값을 저장하거나, 특정 프로퍼티에 대한 유효성 검사를 수행할 때 Property Wrapper를 활용할 수 있습니다.

Property Wrapper를 사용하는 이유와 장점

Property Wrapper를 사용하면 다음과 같은 이점이 있습니다.

  1. 코드 재사용성 증가: 동일한 로직을 여러 프로퍼티에 적용할 때 중복 감소
  2. 가독성 향상: 프로퍼티가 어떻게 동작하는지 명확하게 나타낼 수 있음
  3. 캡슐화: 내부 구현을 감추고, 프로퍼티가 더 명확한 역할을 수행할 수 있도록 함
  4. SwiftUI와의 연계: SwiftUI에서는 @State, @Binding, @ObservedObject 등의 Property Wrapper를 통해 상태 관리가 용이

주요 Property Wrapper와 차이점

Swift에서 제공하는 대표적인 Property Wrapper 몇 가지를 살펴보겠습니다.

@State

  • 정의: SwiftUI에서 사용되는 Property Wrapper로, View 내부에서 상태를 저장할 때 사용됩니다.
  • 특징:
    • View가 상태를 직접 소유하고 관리함.
    • 상태가 변경되면 View가 다시 렌더링됨.
    • 구조체(struct)에서만 사용 가능.
  • 사용 예시
import SwiftUI
struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("\(count)")
            Button("증가") {
                count += 1
            }
        }
    }
}

@Binding

  • 정의: @State와 함께 사용되며, 부모 View의 상태를 자식 View에서 수정할 수 있도록 해줍니다.
  • 특징:
    • @State 변수의 참조를 전달하여, 부모의 상태를 직접 변경할 수 있음.
    • 부모 View의 상태가 변경되면 자식 View도 변경됨.
  • 사용 예시:
struct ParentView: View {
    @State private var count = 0
    
    var body: some View {
        CounterButton(count: $count)
    }
}

struct CounterButton: View {
    @Binding var count: Int
    
    var body: some View {
        Button("증가") {
            count += 1
        }
    }
}

@ObservedObject

  • 정의: ObservableObject 프로토콜을 따르는 클래스를 참조하여 View에서 데이터 변경을 감지할 수 있도록 합니다.
  • 특징:
    • 클래스 기반(class)으로 상태를 저장함.
    • @Published 속성을 사용하여 변경 사항을 알림.
    • View가 @ObservedObject를 참조하고 있을 경우, 해당 객체의 상태가 변경되면 View가 다시 렌더링됨.
  • 사용 예시
class Counter: ObservableObject {
    @Published var count = 0
}

struct CounterView: View {
    @ObservedObject var counter = Counter()
    
    var body: some View {
        VStack {
            Text("\(counter.count)")
            Button("증가") {
                counter.count += 1
            }
        }
    }
}

@EnvironmentObject

  • 정의: View 계층 구조에서 공유되는 객체를 참조할 때 사용됩니다.
  • 특징:
    • @ObservedObject와 유사하지만, 더 상위 계층의 View에서 주입 가능.
    • environmentObject(_:)를 사용하여 부모 View에서 객체를 설정해야 함.
  • 사용 예시
class Counter: ObservableObject {
    @Published var count = 0
}

struct CounterView: View {
    @EnvironmentObject var counter: Counter
    
    var body: some View {
        VStack {
            Text("\(counter.count)")
            Button("증가") {
                counter.count += 1
            }
        }
    }
}

Custom Property Wrapper 만들기

Swift에서는 직접 Property Wrapper를 정의하여 사용할 수도 있습니다. 예를 들어, 특정 값의 최소 및 최대 범위를 제한하는 Property Wrapper를 만들어 보겠습니다.

예제: 범위 제한 Property Wrapper

@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    private let range: ClosedRange<Value>
    
    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = range.contains(wrappedValue) ? wrappedValue : range.lowerBound
    }
    
    var wrappedValue: Value {
        get { value }
        set { value = range.contains(newValue) ? newValue : (newValue < range.lowerBound ? range.lowerBound : range.upperBound) }
    }
}

사용 예시

struct Player {
    @Clamped(0...100) var health: Int = 50
}

var player = Player()
player.health = 120  // 자동으로 100으로 조정됨
player.health = -10  // 자동으로 0으로 조정됨

마무리

Swift의 Property Wrapper는 상태 관리 및 데이터 처리 로직을 캡슐화하여 코드의 가독성을 높이고 유지보수를 용이하게 만듭니다. 기본적으로 제공되는 @State, @Binding, @ObservedObject 등의 Wrapper를 활용하면 SwiftUI에서 더욱 효율적인 상태 관리를 할 수 있습니다. 또한, 필요에 따라 Custom Property Wrapper를 정의하여 다양한 상황에 맞춰 활용할 수도 있습니다.

반응형