Property Wrapper

Dophi·2022년 12월 4일
0

iOS

목록 보기
1/5

소개글

iOS 개발을 하면서 헷갈렸던 개념들을 다시 정리해보고 있습니다.
만약 틀린 내용이 있다면 피드백은 언제나 환영합니다.
더 자세하고 알고싶다면 아래쪽 참고사이트에서 확인하면 좋을 것 같습니다!
말투는 편한 말투로 작성하니 양해 부탁드립니다.

Property Wrapper

사용하는 이유

Property Wrapper 이용해서 특정 변수가 변경될시 자동으로 뷰 업데이트 가능 (데이터 주도적 방식)

종류

@State

  • 지속적으로 변경 가능한 변수 만들 때 사용
  • 주로 String, Int, Bool 같이 간단한 타입에만 사용하는 편
  • 일반적으로 private으로 선언되며, 다른 view와 공유되지 않음
import SwiftUI

struct SampleView: View {
    @State private var count: Int = 0
    
    var body: some View {
        VStack {
            Text("This is SampleView")
            Text("Count: \(count)") 
            
            // 버튼을 눌러서 count 변수의 값을 바꾸기
            // State 변수가 바뀌면 UI도 새롭게 업데이트됨
            Button {
                count += 1
            } label: {
                Text("Increase count")
            }
        }
    }
}

@Binding

  • 다른 뷰의 @State 변수와 연동되도록 함
  • @State 변수명 앞에 $를 붙여서 넘겨주면 됨
import SwiftUI

struct SampleView: View {
    @State private var count: Int = 0
    
    var body: some View {
        NavigationView {
            VStack {
                Text("This is SampleView")
                Text("Count: \(count)")
                
                // 버튼을 눌러서 count 변수의 값을 바꾸기
                Button {
                    count += 1
                } label: {
                    Text("Increase count")
                }
                
                // 버튼을 눌러서 BindingView로 이동하기
                NavigationLink {
                	// $ 표시로 binding임을 명시
                    BindingView(count: $count)
                } label: {
                    Text("Move to BindingView")
                }
                
            }
        }
    }
}

struct BindingView: View {
    @Binding var count: Int
     
    // init에서 직접 Binding 변수를 할당하고 싶을 경우는 아래처럼 작성 가능
    /*
    init(count: Binding<Int>) {
        self._count = count
    } 
    */
    
    var body: some View {
        VStack {
            Text("This is BindingView")
            Text("Count: \(count)")
            
            // 버튼을 눌러서 count 변수의 값을 바꾸기
            // Binding 변수가 바뀌면 SampleView의 State 변수도 함께 바뀜
            Button {
                count += 1
            } label: {
                Text("Increase count")
            }
        }
    }
}

@Published

  • 클래스 안에서만 사용 가능 (구조체 X)
  • Publisher를 자동으로 생성해줌
  • 해당 변수를 구독할 경우 값이 변할 때 감지하고 특정 코드 실행 가능
  • 클래스와 클래스 사이에서 @Published 값을 감지하고 특정 행동을 하고 싶을 때는 $+변수명으로 접근해서 직접 구독 필요

여기서 Publisher, 구독 같은 개념은 Combine이란 프레임워크와 밀접한 관련이 있습니다. Combine에 대한 내용도 나중에 따로 포스팅하겠습니다.
중요한 내용은 값이 변하는 타이밍을 감지하고 특정 코드를 실행하게 해준다는 것입니다!

import SwiftUI
import Combine

class Publisher {
    @Published var count: Int = 0
    
    init() {
        // 1초 후에 count 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+1.0) {
            self.count += 1
        }
        // 2초 후에 count 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+2.0) {
            self.count += 1
        }
        // 3초 후에 count 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+3.0) {
            self.count += 1
        }
    }
}

class Subscriber {
    private let publisher = Publisher()
    // Combine 관련 코드 (구독을 취소할 수 있도록 함)
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // Publisher 변수인 count를 구독
        // 값이 변할때마다 received로 변한 값이 들어오고 print문이 실행됨
        // 지금은 1초마다 값이 변하기 때문에 1초마다 print문이 실행됨
        publisher.$count
            .sink { received in
                print(received)
            }
            .store(in: &cancellables)
    }
}

struct SampleView: View {
    private let subScriber = Subscriber()
    
    var body: some View {
    	Text("This is SampleView")
    }
}

@ObservedObject

  • 변화를 감지하고 싶은 클래스가 ObservableObject 프로토콜을 만족할 경우, 사용 가능
  • 클래스와 구조체 사이에서 @Published 값을 감지하고 뷰를 변화시키고 싶을 때는 @ObservedObject를 사용하여 구독 가능
import SwiftUI

class Publisher: ObservableObject {
    @Published var count: Int = 0
    
    init() {
        // 1초 후에 count 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+1.0) {
            self.count += 1
        }
        // 2초 후에 count 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+2.0) {
            self.count += 1
        }
        // 3초 후에 count 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+3.0) {
            self.count += 1
        }
    }
}

struct SampleView: View {
    @State private var count: Int = 0
    @ObservedObject private var publisher = Publisher()
    
    // init에서 직접 ObservedObject 객체를 할당하고 싶을 경우는 아래처럼 작성 가능
    /*
    init() {
        self._publisher = ObservedObject(wrappedValue: Publisher())
    }
    */
    
    var body: some View {
        VStack {
            Text("This is SampleView")
            // 자동으로 변화를 감지해서 UI를 업데이트하기 때문에 1초마다 count값이 증가함
            Text("Count: \(publisher.count)")
        }
    }
}

@StateObject

  • @ObservedObject와 굉장히 비슷하지만 별도의 객체로 관리됨
  • @ObservedObject 객체를 가지고 있는 View가 초기화된다면 @ObservedObject 객체도 초기화되서 기존 데이터가 사라질 수 있음
  • @StateObject 객체는 View의 라이프사이클에 상관없이 별개의 메모리 공간에 안전하게 저장됨
  • 사용법은 @ObservedObject -> @StateObject로 바꾸기만 하면 됨

@EnvironmentObject

  • 앱 전반에 걸쳐 공유되는 객체를 만들 때 주로 사용
  • @StateObject (또는 @ObservedObject)로 선언된 객체를 .environmentObject()로 넘김
  • 루트 뷰에서 제공하면, 하위 뷰들은 모두 사용 가능
  • 하위뷰에서는 @EnvironmentObject 객체의 타입과, 넘겨진 객체들의 타입을 비교해서 같은 것을 받아옴
import SwiftUI

class Publisher: ObservableObject {
    @Published var count: Int = 0
    
    init() {
        // 5초 후에 카운트 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+5.0) {
            self.count += 1
        }
        // 6초 후에 카운트 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+6.0) {
            self.count += 1
        }
        // 7초 후에 카운트 증가시킴
        DispatchQueue.main.asyncAfter(deadline: .now()+7.0) {
            self.count += 1
        }
    }
}

struct SampleView: View {
    @StateObject private var publisher = Publisher()

    var body: some View {
        NavigationView {
            VStack {
                Text("This is SampleView")
                
                // 버튼을 눌러서 ParentView로 이동하기
                NavigationLink {
                    // publisher 객체 넘겨주기
                    ParentView()
                        .environmentObject(publisher)
                } label: {
                    Text("Move to ParentView")
                }
            }
        }
    }
}

struct ParentView: View {
    // 넘겨진 객체들 중 Publisher 타입의 객체를 받아옴
    @EnvironmentObject private var publisher: Publisher
    
    var body: some View {
        VStack {
            Text("This is ParentView")
            // ChildView로 publisher를 넘기는 코드를 따로 작성하지 않아도 됨
            // 만약 navigation으로 이동하는 뷰라면 .environmentObject로 다시 넘겨줘야함
            ChildView()
        }
    }
}

struct ChildView: View {
    // 넘겨진 객체들 중 Publisher 타입의 객체를 받아옴
    @EnvironmentObject private var publisher: Publisher
    
    var body: some View {
        VStack {
            Text("This is ChildView")
            Text("Count: \(publisher.count)")
        }
        .background(Color.cyan)
    }
}

참고 사이트

https://medium.com/hcleedev/swift-observedobject와-stateobject-4f851ed9ef0d
https://www.hohyeonmoon.com/blog/swiftui-data-flow/
https://stackoverflow.com/questions/61804474/why-cant-swiftui-distinguish-2-different-environment-objects
https://dblog.tech/21

profile
개발을 하며 경험한 것들을 이것저것 작성해보고 있습니다!

0개의 댓글

관련 채용 정보