Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

J

[Swift & SwiftUI] PropertyWrapper 본문

Swift & SwiftUI

[Swift & SwiftUI] PropertyWrapper

yujaehui 2024. 4. 29. 15:16

1. Property Wrapper

PropertyWrapper는 Swift에서 속성의 동작을 캡슐화하여 속성 접근과 변경에 특정 로직을 추가할 수 있도록 지원하는 기능.

반복적인 코드를 줄이고 속성의 동작을 추상화하여 재사용 가능한 구조를 제공.

@propertyWrapper를 사용해 정의되며, SwiftUI의 @State, @Binding, @AppStorage 등이 대표적인 예.


2. Property Wrapper 기본 구조

@propertyWrapper
struct ClampedValue {
    private var value: Int
    private let range: ClosedRange<Int>
    
    var wrappedValue: Int {
        get { value }
        set { value = min(max(range.lowerBound, newValue), range.upperBound) }
    }

    init(wrappedValue: Int, range: ClosedRange<Int>) {
        self.range = range
        self.value = range.contains(wrappedValue) ? wrappedValue : range.lowerBound
    }
}

struct Example {
    @ClampedValue(range: 0...10) var level: Int = 5
}

var example = Example()
example.level = 15
print(example.level) // 출력: 10 (범위 내에서 출력)

주요 구성 요소

  1. wrappedValue
    • 속성의 값을 정의하거나 읽는 데 사용.
    • 내부적으로 캡슐화된 값을 관리하고, 값을 읽거나 쓸 때 추가 로직을 수행할 수 있음.
  2. 초기화
    • init(wrappedValue:)를 사용해 기본값 설정과 함께 속성을 초기화.
  3. 속성 정의
    • @ClampedValue를 사용해 속성을 정의하면 자동으로 캡슐화된 로직이 적용.

3. Property Wrapper 추가 기능: projectedValue

PropertyWrapper는 projectedValue라는 추가 속성을 제공하여 속성 값 외에 부가 정보를 노출할 수 있음.

이는 $를 사용해 접근 가능.

@propertyWrapper
struct ExampleWrapper {
    var wrappedValue: Int
    var projectedValue: String {
        return "Projected value for \\(wrappedValue)"
    }
}

struct Example {
    @ExampleWrapper var value: Int = 42
}

let example = Example()
print(example.value) // 출력: 42 (wrappedValue)
print(example.$value) // 출력: "Projected value for 42" (projectedValue)

SwiftUI의 @State와 @Binding의 projectedValue 측면 탐구

2024.04.29 - [SwiftUI] - [SwiftUI] @State와 @Binding의 projectedValue ($)


4. 일반적인 사용 사례

UserDefaults와 연동

UserDefaults를 간편하게 관리할 수 있도록 속성 래퍼를 구현.

@propertyWrapper
struct UserDefault<T> {
    private let key: String
    private let defaultValue: T

    init(wrappedValue: T, key: String) {
        self.defaultValue = wrappedValue
        self.key = key
    }

    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }

    var projectedValue: String {
        return "Key: \\(key), Default: \\(defaultValue)"
    }
}

struct Settings {
    @UserDefault(wrappedValue: false, key: "isDarkMode") var isDarkMode: Bool
}

let settings = Settings()
print(settings.isDarkMode)   // 출력: false
print(settings.$isDarkMode)  // 출력: Key: isDarkMode, Default: false

값 유효성 검사

값의 유효성을 검사하여 잘못된 입력을 방지.

@propertyWrapper
struct ValidatedString {
    private var value: String
    private let maxLength: Int

    init(wrappedValue: String, maxLength: Int) {
        self.maxLength = maxLength
        self.value = String(wrappedValue.prefix(maxLength))
    }

    var wrappedValue: String {
        get { value }
        set { value = String(newValue.prefix(maxLength)) }
    }

    var projectedValue: String {
        return value.count <= maxLength ? "Nickname of valid length" : "Nickname of invalid length"
    }
}

struct User {
    @ValidatedString(maxLength: 10) var username: String = "DefaultUser"
}

var user = User()

// 값 설정
user.username = "VeryLongUsernameExceedingLimit"
print(user.username)   // 출력: VeryLongUs
print(user.$username)  // 출력: Nickname of valid length

// 유효한 값 설정
user.username = "ShortName"
print(user.username)   // 출력: ShortName
print(user.$username)  // 출력: Nickname of valid length

4. SwiftUI에서의 활용

SwiftUI는 @State, @Binding, @Environment, @AppStorage 등 다양한 속성 래퍼를 제공하여 상태 관리를 쉽게 함.

@State

  • 뷰 내부의 로컬 상태를 관리하며, 상태가 변경되면 뷰가 다시 렌더링.
struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \\(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

@Binding

  • 상위 뷰의 상태를 하위 뷰에서 읽고 수정할 수 있도록 연결.
struct ParentView: View {
    @State private var isOn = false

    var body: some View {
        ToggleView(isOn: $isOn)
    }
}

struct ToggleView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

@AppStorage

  • UserDefaults와 연결해 데이터를 저장하고 관리.
struct SettingsView: View {
    @AppStorage("username") var username: String = "Guest"

    var body: some View {
        TextField("Username", text: $username)
    }
}

6. PropertyWrapper vs. Generic

  PropertyWrapper Generic
캡슐화 여부 속성 동작 캡슐화 가능 일반화된 타입 로직 정의
사용 용도 특정 속성 동작 반복 제거 및 재사용 여러 타입에서 동일한 로직 재사용
접근성 속성 접근 시 자동으로 동작 적용 호출자가 명시적으로 사용
유연성 속성의 특정 동작을 정의 타입 간 호환성 제공

7. 주요 특징 요약

  • 재사용성
    • 공통된 로직을 캡슐화해 중복 코드 제거 및 유지보수 용이성을 높임.
  • 캡슐화:
    • 값을 읽고 쓸 때 발생하는 로직을 숨겨 속성 사용을 단순화.
  • SwiftUI 통합:
    • SwiftUI의 상태 관리와 자연스럽게 통합되어 UI 업데이트를 자동화.

8. 결론

PropertyWrapper는 속성 관리와 동작을 추상화하여 효율적이고 깔끔한 코드를 작성할 수 있게 함.

캡슐화, 재사용성, 그리고 SwiftUI와의 통합으로 인해 Swift 개발에 있어 필수적인 도구가 되고 있음.

'Swift & SwiftUI' 카테고리의 다른 글

[Swift & SwiftUI] 버전 대응 Wrapper  (0) 2024.05.19
[Swift & SwiftUI] OpaqueType  (0) 2024.04.19