์ถœ์ฒ˜ ๋ฐ ์ฐธ๊ณ  ์‚ฌ์ดํŠธ

https://axiomatic-fuschia-666.notion.site/Chapter-3-TCA-2-c56b24efb2154dad9ed8e54139247024

 

Chapter 3. TCA์˜ ๊ธฐ๋ณธ๊ฐœ๋…(2)

์•ž์„  ์žฅ์—์„œ ์šฐ๋ฆฌ๋Š” ์•ฑ์˜ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” State์™€ ์ด๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜๋‹จ์ธ Action, ๊ทธ Action์˜ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ  ์ƒํƒœ์˜ ๋ณ€๊ฒฝ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Reducer์„ ์•Œ์•„๋ณด๋ฉฐ, TCA์—์„œ์˜ ๋ฐ์ดํ„ฐํ๋ฆ„์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ

axiomatic-fuschia-666.notion.site

What is Store

  • ๋Ÿฐ ํƒ€์ž„๋™์•ˆ Reducer์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ฐธ์กฐ ํƒ€์ž… ๊ฐ์ฒด
  • ์•ฑ์˜ State, Action์„ ๊ด€๋ฆฌ
    • State์˜ ๋ณ€ํ™” ๊ฐ์ง€
    • ์•ก์…˜ ์ฒ˜๋ฆฌ
let store: Store<ProductDetailFeature.State, ProductDetailFeature.Action>
public typealias StoreOf<R: Reducer> = Store<R.State, R.Action>
let store: StoreOf<ProductDetailFeature>

typealias ์ถ•์•ฝ ํ‘œํ˜„

 

ViewStore๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ฒŒ ๋œ ๋ฐฐ๊ฒฝ

  • Store: ์•ฑ์˜ ์ƒํƒœ ๋ณ€ํ™” ๊ด€๋ฆฌ
  • ViewStore: View์— ํ•„์š”ํ•œ ์ƒํƒœ๋งŒ ๊ตฌ๋…ํ•˜๊ณ  ์—…๋ฐ์ดํŠธ
  • View์— ํ•„์š”ํ•˜์ง€ ์•Š์€ ์ƒํƒœ์˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ ๋ถˆํ•„์š”ํ•œ View ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐฉ์ง€
  • MultiStore
    • ์ƒ์œ„ View์˜ Store๋Š” ํ•˜์œ„ View์— ํ•„์š”๋กœ ํ•˜๋Š” ์ƒํƒœ ์ผ๋ถ€๋ฅผ ์†Œ์œ ํ•˜๊ณ  ์žˆ๊ณ , ํ•˜์œ„ View์— ์ด ์ผ๋ถ€๋ฅผ ์†Œ์œ ํ•˜๋Š” ๋ณ„๋„์˜ ์Šคํ† ์–ด๋ฅผ ์—ฐ๊ฒฐํ•˜๊ฒŒ ํ•จ
    • ์ž์‹ View์—์„œ ์•ก์…˜์„ ๋ฐ›์œผ๋ฉด ์ด๋ฅผ ๋ถ€๋ชจ ์Šคํ† ์–ด์— ์ „๋‹ฌ
    • ์•ก์…˜์„ ๋ฐ›์€ ๋ถ€๋ชจ ์Šคํ† ์–ด๋Š” ์ž์ฒด ๋ฆฌ๋“€์„œ ํ˜ธ์ถœ -> ๋‚ด๋ถ€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    • ์•ก์…˜์ด ์ด๋ฃจ์–ด์ง€๋Š” ๋™์•ˆ ๋ถ€๋ชจ ๋ทฐ์˜ ์ƒ์œ„ ๋ทฐ์—์„œ View ๋ Œ๋”๋ง ์š”์ฒญ์ด ์˜ค๋ฉด ์•ก์…˜์œผ๋กœ ์ธํ•œ ๋ Œ๋”๋ง ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ƒ์œ„ ๋ทฐ์˜ ๋ Œ๋”๋ง ์š”์ฒญ์— ์˜ํ•ด ์—ฌ๋Ÿฌ๋ฒˆ ๋ Œ๋”๋ง ํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒ ๋จ
    • but ViewStore๋Š” ๋ณ€ํ™”์˜ ์ค‘๋ณต์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํƒ‘์žฌ -> View์˜ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ๊ด€์ฐฐ x, ์ผ๋ถ€๋ถ„์˜ ๋ณ€ํ™”๋กœ View๋Š” ๊ทธ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
  public init<State>(
    _ store: Store<State, ViewAction>,
    observe toViewState: @escaping (_ state: State) -> ViewState,
    removeDuplicates isDuplicate: @escaping (_ lhs: ViewState, _ rhs: ViewState) -> Bool
  ) {
    self._send = { store.send($0, originatingFrom: nil) }
    self._state = CurrentValueRelay(toViewState(store.stateSubject.value))
    self._isInvalidated = store._isInvalidated
    #if DEBUG
      self.storeTypeName = ComposableArchitecture.storeTypeName(of: store)
      Logger.shared.log("View\(self.storeTypeName).init")
    #endif
    self.viewCancellable = store.stateSubject
      .map(toViewState)
      .removeDuplicates(by: isDuplicate)
      .sink { [weak objectWillChange = self.objectWillChange, weak _state = self._state] in
        guard let objectWillChange = objectWillChange, let _state = _state else { return }
        objectWillChange.send()
        _state.value = $0
      }
  }
  • ์œ„ ์ฝ”๋“œ์—์„œ @escaping (_ state: State) -> ViewState ์—์„œ View์— ํ•„์š”ํ•œ ๋ณ€ํ™”๋ฅผ ๊ด€์ฐฐํ•˜๊ธฐ ์œ„ํ•ด View์— ํ•„์š”ํ•œ State struct๋ฅผ ๊ตฌ์„ฑ
  • ์›๋ณธ State๋ฅผ ๊ด€์ฐฐํ•  ์ƒํƒœ๋กœ ๊ตฌ์„ฑํ•œ ViewState๋กœ ๋ณ€ํ™˜
  • ViewState์˜ ๋ณ€ํ™”๊ฐ€ ๊ฐ์ง€๋˜๋ฉด SwiftUI View์—๊ฒŒ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ์•Œ๋ฆผ
  • isDuplicate๋กœ ๋ณ€ํ™”์˜ ์ค‘๋ณต์„ ์—†์•ค ๋’ค ๋‚ด๋ถ€์˜ State๊ฐ’์„ ๊ฐฑ์‹ 

WithViewStore

  • Reducer ๊ตฌ์„ฑ ํ›„ WithViewStore๋กœ Store์™€ View ์—ฐ๊ฒฐ
    let store: StoreOf<ProductDetailFeature>
    
    var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in
            // ๊ธฐ์กด ๋ฐฉ์‹
      	}
    }
  • ๊ธฐ์กด ๋ฐฉ์‹์€ ํƒ€์ž…์„ ๋ช…์‹œํ•ด์„œ ์ปดํŒŒ์ผ๋Ÿฌ์˜ ์—ฐ์‚ฐ์„ ์ค„์ผ ์ˆ˜ ์žˆ์Œ
    let store: StoreOf<ProductDetailFeature>
    @ObservedObject var viewStore: ViewStoreOf<ProductDetailFeature>
    
    init(store: StoreOf<ProductDetailFeature>) {
        self.store = store
        self.viewStore = ViewStore(self.store, observe: { $0 })
    }
  • ์ด๋‹ˆ์…œ๋ผ์ด์ €๋กœ Store์„ ์ฃผ์ž…๋ฐ›์Œ
  • ์ด๋‹ˆ์…œ๋ผ์ด์ € ๋‚ด๋ถ€์—์„œ ViewStore์„ ์ƒ์„ฑ
  • View ๊ณ„์ธต ๊ตฌ์กฐ์— ์ ์–ด ์ฃผ์—ˆ๋˜ WithViewStore ~ ์„ ์ค„์ผ ์ˆ˜ ์žˆ์–ด ๊ธฐ์กด ๋ณด๋‹ค View ๊ณ„์ธต์„ ์ค„์ผ ์ˆ˜ ์žˆ์Œ
@dynamicMemberLookup
public final class ViewStore<ViewState, ViewAction>: ObservableObject { /* ... */ }

 

  N.B. `ViewStore` does not use a `@Published` property, so `objectWillChange`
  won't be synthesized automatically. To work around issues on iOS 13 we explicitly declare it.

Scope

  public func scope<ChildState, ChildAction>(
    state toChildState: @escaping (_ state: State) -> ChildState,
    action fromChildAction: @escaping (_ childAction: ChildAction) -> Action
  ) -> Store<ChildState, ChildAction> {
    self.scope(state: toChildState, action: fromChildAction, removeDuplicates: nil)
  }
  • Store์˜ scope ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ํ•˜์œ„ State ๋ฐ Action์„ ๋‹ค๋ฃจ๋Š” ์Šคํ† ์–ด๋กœ ๋ณ€ํ™˜
  • Store์„ ์ž‘์€ ๋ฒ”์œ„์˜ Store๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Œ
    • ํ•ด๋‹น ๋„๋ฉ”์ธ์˜ ์ƒํƒœ์™€ ์•ก์…˜์„ ์ถ”์ถœํ•œ ์ž‘์€ ๋ฒ”์œ„์˜ Store์„ ๋ฐ˜ํ™˜
    • ๊ฐ View์˜ ํ•„์š”ํ•œ ์ƒํƒœ์™€ ์•ก์…˜์„ ์ •์˜ํ•˜๊ณ  ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ
    • ๋ชจ๋“ˆํ™”์™€ ์œ ์—ฐ์„ฑ
    • ์œ ๋‹› ํ…Œ์ŠคํŠธ ์šฉ์ด
  • state: ์ƒํƒœ๋ฅผ ์ถ”์ถœํ•  KeyPath๋กœ์„œ ์ƒ์œ„ ๋ทฐ์˜ state๋ฅผ ์ถ”์ถœ (WritableKeyPath)
  • action: ์•ก์…˜์„ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•œ KeyPath (CasePath)
AmountControlView(
    store: self.store.scope(state: \.amountControl, action:
ProductDetailFeature.Action.amountControl)
)
  • self.store๋Š” ์ƒ์œ„ ๋ทฐ์ธ ProductDetailFeature์˜ Store

Scope ๋” ์•Œ์•„๋ณด๊ธฐ

2023.11.30 - [Architecture] - [SwiftUI/TCA] Scope

 

 

Pexels์—์„œ Pixabay๋‹˜์˜ ์‚ฌ์ง„: https://www.pexels.com/ko-kr/photo/327482/