관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] SwiftUI의 Binding 이해하기, @Binding 구현 방법 (#protocol에서 get만 제공하는 프로퍼티에 set 기능 부여하기) 본문

iOS 응용 (swift)

[iOS - swift] SwiftUI의 Binding 이해하기, @Binding 구현 방법 (#protocol에서 get만 제공하는 프로퍼티에 set 기능 부여하기)

jake-kim 2023. 7. 31. 01:29

SwiftUI의 Binding

  • 바인딩이란 A가 변경될 때 같이 B도 변경해주어야하는 경우, A와 B를 바인딩 해놓으면 A가 변경되었을때 B는 자동으로 변경되게끔 하는 기법을 의미
  • SwiftUI에서의 바인딩은 property wrapper 형태로 존재

https://developer.apple.com/documentation/swiftui/binding

  • Binding 인터페이스
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen @propertyWrapper @dynamicMemberLookup public struct Binding<Value> {
    public var transaction: Transaction
    
    public init(get: @escaping () -> Value, set: @escaping (Value) -> Void)
    public init(get: @escaping () -> Value, set: @escaping (Value, Transaction) -> Void)
    
    public static func constant(_ value: Value) -> Binding<Value>
    
    public var wrappedValue: Value { get nonmutating set }
    public var projectedValue: Binding<Value> { get }
    
    public init(projectedValue: Binding<Value>)
    
    public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> { get }
}
  • 핵심적인 요소만 보면 생성자에 get과 set 클로저를 받는 형태이고, get이 발생하면 내부적으로 가지고 있는 value를 리턴해주고, set이 발생하면 내부적으로 가지고 있는 value의 값을 변경하는 형태임을 추측이 가능
@propertyWrapper 
struct Binding<Value> {
    public init(get: @escaping () -> Value, set: @escaping (Value) -> Void)
}

직접 Binding 구현해보기

  • SwiftUI의 @Binding을 똑같이 만들다기보단, Binding 동작을 이해하는 것이 목적이므로 가장 간단하게 Binding을 구현
    • 예상한대로 get, set 클로저만 기록하고 있고 이 프로퍼티의 값은 외부 property의 값에 따라 자동으로 반영되도록 바인딩하는 역할
@propertyWrapper
struct MyBinding<T> {
    private var get: () -> T
    private var set: (T) -> Void

    var wrappedValue: T {
        get { get() }
        nonmutating set { set(newValue) }
    }
    
    init(get: @escaping () -> T, set: @escaping (T) -> Void) {
        self.get = get
        self.set = set
    }
}

ex) _age의 값을 변경하면 자동으로 ageBinding.wrappedValue의 값도 변경되도록하는 코드

var _age = 0

var ageBinding: MyBinding<Int> = {
    MyBinding(get: {
        _age
    }, set: {
        _age = $0
    })
}()

print(ageBinding.wrappedValue)
_age = 2
print(ageBinding.wrappedValue)

/*
 0
 2
 */

Binding을 이용하여 protocol의 get만 제공하는곳에 set 추가하기

  • 아래와 같은 protocol이 있을 때 age를 수정할 수 없는점이 존재
protocol Peronable {
    var age: Int { get }
}

var person: Peronable
  • property에 set을 추가하여 setter 기능을 만드는 것이 일반적이지만, Peronable은 공통으로 여러곳에서 사용하고 있었고 setter를 추가하면 다른 곳에 사이드 이펙을 줄 때 다른 방법이 필요
    • 위에서 알아본 MyBinding을 사용하면 set 키워드 없이 사용이 가능
  • Person 모델에 @MyBinding 키워드 사용
struct Person: Peronable {
    @MyBinding var age: Int
}
  • person은 Peronable 타입이지만 아래처럼 _age 프로퍼티를 만들고 이 값을 바인딩하면 setter 키워드 추가 없이, _age가 변경되면 자동으로 person 값에 setter가 가능
var _age = 0
var ageBinding: MyBinding<Int> = {
    MyBinding(get: {
        _age
    }, set: {
        _age = $0
    })
}()

var person: Peronable = {
    Person(age: ageBinding)
}()

결과)

print(ageBinding.wrappedValue)
_age = 2 // setter가 가능
print(ageBinding.wrappedValue)

/*
0
2
*/

* 전체 코드: https://github.com/JK0369/ExBinding

*  참고

https://developer.apple.com/documentation/swiftui/binding

Comments