관리 메뉴

사과하는 제라스

[Swift 문법] @Bindable에 대하여(feat. Binding) 본문

제라스의 Swift 공부/Swift 문법

[Swift 문법] @Bindable에 대하여(feat. Binding)

Xerath(제라스) 2024. 3. 11. 01:52

목차

    728x90
    반응형

    서론

    하이루~~ 안녕하세요 개발자 제라스입니다. 👋🏻🤖👋🏻

    요즘 통 업로드가 적었는데 조만간 업로드 될 테지만 사실 작년 네이버 부스트캠프 웹•모바일 8기를 수료한 후

    지금은 포항에서 Apple Developer Academy 3기를 하고 있습니다.

     

    사실 3주 전에 제 학사모를 고이 보내드리고 이젠 어엿하지 않은데 어엿한 사회인인 척 해야하는 어엿 호소인이 되었습니다.

    이후 잠시 포항에 내려오기 전에 여행 좀 하고 심적 여유를 가지려고 하다보니 조금 포스팅이 늦어졌네요.

     

    무튼 무튼, 준비도 할 게 많고 네트워킹도 하고 하다보니까 지금도 올릴 포스팅들이 밀려있네요.

    막 이거 공부해서 써야지~했는데 죄다 쓰다만...

     

     

    조만간 여러가지 주제로 조금씩 준비하던 것들을 업로드해보겠습니다!

     

    사실 제가 이곳에 와서 처음으로 SwiftUI를 접해봤어요. 제대로 써본 경험이 없다보니까 정말 뚝딱이 그 자체네요.

    뚝딱대는 저입니다. 주황색이 Swift랑 참 잘 어울리는 배경이네요.

    그래서 요즘은 무작정 SwiftUI 공식 튜토리얼을 따라하면서 주변 지식들을 알아가는 중인데 가장 눈에 띄는게 @Bindable이더라구요?

    사실 저는 SwiftUI를 처음 공부해보는데 이 Bindable이란 친구가 iOS17부터 적용되는 새로운 녀석이더라구요.

    그래서 더욱 관심이 갖습니다.

     

    다들 혹시 작년에 아주 핫했던 '매크로' 기억이 나시나요?

    저는 아직 매크로에 대한 학습을 못했는데요...😭😭 작년에 잠시 봤던 소들이, 날진님의 매크로 KWDC 영상본게 다입니다...껄껄

    연습해라, 공부해라, 성실해라 라스야...

     

    그래서 무한 G.O.O.G.L.I.N.G을 해본 결과...

    매크로가 도입되면서 사용하는 Propery Wrapper가 많이 변했다고 합니다.

    그러면서 자연스레 최근 Apple에서 올려둔 SwiftUI Tutorial에서는 @Bindable을 활용하여 코드를 작성하더라구요.

     

    그럼 오늘은 Bindable이 무엇인지, 그리고 이것을 어떻게 사용하는지 예시와 사용하는 이유에 대한 개인적인 견해를 적어보겠습니다.

     

    애플 공식 튜토리얼에서 사용하는 Bindable

    제가 애플 공식 SwiftUI Tutorial을 보면서 눈이 간 코드가 있다고 했죠?

    다음이 제가 @Bindable에 작게 나마 딥다이브를 하게 만든 부분입니다.

    //
    //  LandmarkDetail.swift
    //  Landmarks
    //
    //  Created by 윤동주 on 3/6/24.
    //
    
    import SwiftUI
    
    struct LandmarkDetail: View {
        @Environment(ModelData.self) var modelData // <-- 1. 이렇게 프로젝트 전역에 뿌린 데이터 ModelData를 modelData로 읽어오고.
        
        var landmark: Landmark
        
        var landmarkIndex: Int {
            modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
        }
        
        var body: some View {
            @Bindable var modelData = modelData // <-- 2. Bindable을 달아서 받아오면
            
            ScrollView {
                MapView(coordinate: landmark.locationCoordinate)
                    .frame(height: 300)
                
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                    .padding(.bottom, -130)
                
                VStack(alignment: .leading) {
                    HStack {
                        Text(landmark.name)
                            .font(.title)
                        FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite) // <-- 3. 이렇게 Binding하는 효과로 넣어주는 방식입니다.
                    }
                    
                    HStack {
                        Text(landmark.park)
                        Spacer()
                        Text(landmark.state)
                    }
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    
                    Divider()
                    
                    Text("About \(landmark.state)")
                        .font(.title2)
                    
                    Text(landmark.description)
                }
                .padding()
                
            }
            .navigationTitle(landmark.name)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
    
    struct FavoriteButton: View {
        @Binding var isSet: Bool
        
        var body: some View {
            Button {
                isSet.toggle()
            } label: {
                Label("Toggle Favorite", systemImage: isSet ? "star.fill" : "star")
                    .labelStyle(.iconOnly)
                    .foregroundColor(isSet ? .yellow : .gray)
            }
        }
    }
    
    #Preview {
        let modelData = ModelData()
        return LandmarkDetail(landmark: modelData.landmarks[0])
            .environment(modelData)
    }

     

    코드가 너무 길죠...? 1,2,3 주석 달아둔 부분만 확인해보시면 됩니다.

    그렇다면 만약 예전처럼 Bindable을 안쓴다면 어떻게 쓰길래 다르냐?? 라고 하신다면

    TA-DA~

    이렇게 바꿀 수가 있습니다.

    //
    //  LandmarkDetail.swift
    //  Landmarks
    //
    //  Created by 윤동주 on 3/6/24.
    //
    
    import SwiftUI
    
    struct LandmarkDetail: View {
        @Environment(ModelData.self) var modelData // <-- 1. 이건 똑같이 프로젝트 전역에 뿌려진 modelData를 가져오구요.
        
        var landmark: Landmark
        
        var landmarkIndex: Int {
            modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
        }
        
        var body: some View {
            
            ScrollView {
                MapView(coordinate: landmark.locationCoordinate)
                    .frame(height: 300)
                
                CircleImage(image: landmark.image)
                    .offset(y: -130)
                    .padding(.bottom, -130)
                
                VStack(alignment: .leading) {
                    HStack {
                        Text(landmark.name)
                            .font(.title)
                        FavoriteButton(isSet: Binding(get: {
                            modelData.landmarks[landmarkIndex].isFavorite
                        }, set: { newValue in
                            modelData.landmarks[landmarkIndex].isFavorite = newValue
                        })) // <-- 2. modelData를 Binding으로 감싸주고 get, set을 각각 명시해줍니다.
                    }
                    
                    HStack {
                        Text(landmark.park)
                        Spacer()
                        Text(landmark.state)
                    }
                    .font(.subheadline)
                    .foregroundColor(.secondary)
                    
                    Divider()
                    
                    Text("About \(landmark.state)")
                        .font(.title2)
                    
                    Text(landmark.description)
                }
                .padding()
                
            }
            .navigationTitle(landmark.name)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
    
    #Preview {
        let modelData = ModelData()
        return LandmarkDetail(landmark: modelData.landmarks[0])
            .environment(modelData)
    }

     

    보시다시피 기존의 방식은 생각보다 귀찮은 점이 존재하죠.

    일단 SearchField라는 곳에 Binding 되지 않은 채 들어가면 안되기에 Binding 처리를 해줘야 하고요.

    이 Binding 처리를 해주기 위해 get,set을 다 설정해줘야 하니까요.

     

    근데, 이걸 단순히 @Bindable만 처리하면 해결이 된다? 정말 편해진거죠.

    다른 편해진 부분은 뭘까?

    Bindable에 대해 조금 더 찾아보다보니 HackingWithSwift에 변화된 부분이 나오더라구요.

    https://www.hackingwithswift.com/quick-start/swiftdata/whats-the-difference-between-bindable-and-binding

    위 링크에 들어가시면 훨씬 더 괜찮은 도움을 받으실 수 있는데 간단히 요약해보자면...

     

    사실 @Bindable로 처리를 하면 Binding되는 @Observable 객체 뿐만 아니라 그 내부의 각 프로퍼티들도 Binding 처리가 됩니다.

    그렇기 때문에 $표시로 달아서 각 프로퍼티들을 바로 써도 됩니다.

    (처음에 올린 코드의 3번 주석을 참고해주시면 됩니다~!)

     

    즉, 우리는 @Observable 객체에서 각 속성들을 따로 Binding 처리할 필요없이 @Bindable로 가볍게 모두 Binding해줄 수 있습니다. 이게 우리가 왜 @Bindable을 써야 하는지에 대한 이유인 것 같습니다.

    그럼 Binding을 Bindable이 대체하는 건가?

    결론부터 말씀드리면 그렇지 않습니다.

    왜냐하면 결국은 끝단에서 반드시 @Binding을 해서 연결해야 하기 때문입니다.

    간단히 보자면 과거에는 Binding-Binding-Binding-...-Binding과 같은 처리였다면,

    지금은 필요에 따라 Bindable-...-Binding으로 해주면 됩니다.

     

    즉, 아예 대체한다는 개념이 아니고 좋은 옵션이 생겼구나, 좋은 장치가 생겼구나 정도로 이해하는게 맞다고 봅니다.

    마무리

    이렇게 오늘은 @Bindable에 대한 학습을 해보았습니다.

    사실 아직 SwiftUI에 대한 학습이 제대로 이뤄지지 않아서 이것이 과연 필요한 딥다이브일지는 모르겠습니다.

    하지만 뭔가 새롭게 변화된 부분이고 주변에 Apple Developer Academy에서 만난 러너 분들과 얘기를 나눠보면서 이걸 쓰면서 데이터의 흐름이 자연스러워졌다는 얘기도 들었기에 한번쯤 공부해보면 좋지 않을까 했습니다.

     

    @Bindable이 애플이 추구하는 방향성에서 어떤 역할을 해주길 기대해야 하는지는 조금 더 깊게 딥다이브가 필요하겠지만 지금으로서는 매크로에 대한 학습, 기존의 Property Wrapper들에 대한 학습이 이뤄진 후 해도 늦지 않다고 생각해서 추후 개선된 정리를 해보려고 합니다.

     

    뭔가 Bindable 관련해서 공부를 하면서 명쾌한 대답이나 공부가 되지는 않았던 것 같아요.

    그래서 이 부분을 조금 더 여유를 갖고 알아가보고자 합니다.

    조금 더 딥다이브해서는 KVO, 매크로, Bindable의 성능적인 면까지 학습해보려구요.

     

    관련해서 쓸게 될 포스팅에서는 더 괜찮고 명쾌한 글로 돌아오겠습니다.

    끝까지 읽어주셔서 감사합니다!

    참고

    https://www.hackingwithswift.com/quick-start/swiftdata/whats-the-difference-between-bindable-and-binding

    https://youtu.be/YgrnC1hIFEY?si=O0NC8eQwiCW4rxTh

     


    -> 개인적으로 해당 영상을 보면서 정리해보았는데 순서대로 따라하시다보면 어떤 변화가 생겼는지 알 수 있습니다.

    사실 지금 생각해보면 단순히 코드적인 면에서 사용법의 변화 정도를 알 수 있는 듯하고 그 세부적인 이유는 공식문서나 다른 아티클들을 참고하시면 좋을 것 같습니다ㅎㅎ

     


    아직 꼬꼬마 개발자입니다.

    더 나은 설명이나 코드가 있다면 언제든 환영입니다.

    적극적인 조언과 피드백 부탁드립니다!

     

    그럼 오늘도 개발 가득한 하루되세요!

    - Xerath -

    🤖🤖🤖🤖🤖🤖🤖

     

    728x90
    반응형