๐Ÿคฏ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž ์ถฉ๊ฒฉ! iOS TCA ์‚ดํŽด๋ณด๊ธฐ. ์ด ๋ฐฉ์‹์ด ๋งž๋Š”๊ฑธ๊นŒ? ๐Ÿคฏ

๐Ÿคฏ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž ์ถฉ๊ฒฉ! iOS TCA ์‚ดํŽด๋ณด๊ธฐ. ์ด ๋ฐฉ์‹์ด ๋งž๋Š”๊ฑธ๊นŒ? ๐Ÿคฏ



๊ฐœ์ธ ๊ด‘๊ณ  ์˜์—ญ

์ด ๊ธ€์€ Android ๊ฐœ๋ฐœ์ž์ธ ํ•„์ž๊ฐ€ TCA ์ผ๋ถ€๋ฅผ ์‚ฌ์šฉํ•ด ๋ณด๊ณ  ๊ฒฝํ—˜ํ•œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ •๋ฆฌํ•˜์˜€๊ณ , ์ด ๋ฐฉ์‹์ด ๋งž๋Š” ๊ฒƒ์ผ๊นŒ์˜ ๋‚ด์šฉ์„ ํฌํ•จํ•œ๋‹ค.

์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋‹ค๋ฉด ์ ๊ทน ์•Œ๋ ค์ฃผ๋ฉด ์ข‹๊ฒ ๋‹ค.

TCA๋Š” iOS์—์„œ ์š”์ฆ˜ ๋งŽ์ด ํ™œ์šฉํ•˜๊ณ  ์žˆ๋Š” The Composable Architecture์ด๋‹ค.

TCA๋Š” ๋‹ค์Œ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค.

  • State management
  • Composition
  • Side effects
  • Testing
  • Ergonomics

ํ•˜์ง€๋งŒ ์‹ค์ œ ์‚ฌ์šฉํ•ด ๋ณด๋ฉด ๋ฆฌ๋•์Šค๋ผ๊ณ  ๋ณด๋Š” ๊ฒƒ์ด ๋งž๋‹ค. ์›น์—์„œ๋Š” ๋ฆฌ๋•์Šค๊ฐ€ ๋„ˆ๋ฌด ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฑธ๋กœ ๋“ค์—ˆ๋‹ค.

์‹ค์ œ TCA๋„ ๋งค์šฐ ๋ณต์žกํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ swiftUi์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ์—๋Š” ์ข‹๋‹ค๊ณ  ํ•œ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ€๋ณ๊ฒŒ TCA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฒฝํ—˜ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด ๋ณธ ๊ธ€์ด๋‹ˆ ์ฐธ๊ณ ๋งŒ ํ•˜์‹œ๊ธธ.(์˜ค๋ฅ˜๋Š” ์–ธ์ œ๋‚˜ ๋Œ“๊ธ€๋กœ)

์ด ๊ธ€์—์„œ๋Š”

  • ํ•„์ž๊ฐ€ ์ƒ๊ฐํ•˜๋Š” Composable ๊ตฌ์กฐ๋Š”?
  • ๊ฐ„๋‹จํ•œ ์ƒ˜ํ”Œ ์ฝ”๋“œ๋ฅผ ํ†ตํ•œ ์•Œ์•„๋ณด๊ธฐ

ํ•„์ž๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ํŽธํ•œ ๊ตฌ์กฐ

ํ•„์ž๋Š” ๊ฐœ๋ฐœํ•˜๊ธฐ ํŽธํ•œ ๋ฐฉ์‹์„ ์„ ํ˜ธํ•˜๋Š” ํŽธ์ด๋‹ค.

๋ณธ ๋ธ”๋กœ๊ทธ์—์„œ๋„ ์—ฌ๋Ÿฌ ๋ฒˆ ์ ์—ˆ๋˜ ๋‚ด์šฉ์ด์ง€๋งŒ ํ•„์ž์˜ ์„ ํ˜ธ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

ํ™”๋ฉด์˜ ํŠน์ • View๋ฅผ ๊ณตํ†ตํ™”ํ•œ๋‹ค๋Š” ๊ฐ€์ •์„ ํ•ด๋ณด์ž.

ex) back ๋ฒ„ํŠผ์€ ์–ด๋Š ํ™”๋ฉด์—์„œ๋‚˜ ์ด์ „ ํ™”๋ฉด์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ณตํ†ต์˜ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค.

  • ์ฐธ๊ณ  : ์—ฌ๊ธฐ์„œ back ๋ฒ„ํŠผ์„ ์„ค๋ช…ํ•˜๋Š” ์ด์œ ๋Š” ๋ˆ„๊ฐ€ ๋ณด์•„๋„ ๊ฐ€์žฅ ๊ณตํ†ตํ™” ์‹œํ‚ค๊ธฐ ์‰ฌ์šด ๊ตฌ์กฐ๋ผ ์ƒ๊ฐํ•˜์—ฌ ์„ ํƒํ•˜์˜€๋‹ค.

๋‹จ,

  • Back ๋ฒ„ํŠผ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋”ฉ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ Back๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • Back ์ด๋ฒคํŠธ ์‹œ์—๋Š” ์ด์ „ ํ™”๋ฉด์œผ๋กœ์˜ ์ด๋™์„ ํ•˜๊ฑฐ๋‚˜, ์ƒํ™ฉ์— ๋”ฐ๋ผ alert ์„ ์งˆ๋ฌธ์„ ๋ฐ›๊ณ  ์ข…๋ฃŒํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

์œ„์™€ ๊ฐ™์€ ๋‹จ์„œ๋„ ๋‹ฌ์•„๋ณด์•˜๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ํ•„์ž๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

  1. View์—๋Š” Back ๋ฒ„ํŠผ ๋งŒ์„ ํฌํ•จํ•˜๋Š” Composable ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  2. View์— ๋Œ€ํ•œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณณ์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค.
  3. router๋ฅผ ํ†ตํ•ด ์ด์ „ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•˜๋ผ๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ์ผ์–ด๋‚œ๋‹ค.
    • ์—ฌ๊ธฐ์„œ ์ด์ „์ด๋ผ๋Š” ๊ฐœ๋…์€ ๋„ค๋น„๊ฒŒ์ด์…˜
    • ๋„ค๋น„๊ฒŒ์ด์…˜์— ์ฃผ์ž…ํ•ด๋‘” ์ •๋ณด์— ๋”ฐ๋ผ Back์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์ด๋Ÿฌ๋ฉด ๊ณตํ†ต์˜ View๊ฐ€ ํ•˜๋‚˜ ๋‚˜์™”๋‹ค. ๋ˆ„๊ตฌ๋‚˜ ์ข‹์•„ํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ๋„ ๋”ฐ๋ž๊ณ , View๋งŒ์„ ์Šค์Šค๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ ๋“œ๋Š” ์˜๋ฌธ์ ?

back์„ ํ–ˆ๋Š”๋ฐ ์ด ์ด๋ฒคํŠธ๋Š” ๋ˆ„๊ฐ€ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฐ€? ์Šค์Šค๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค๊ณ  ํ•˜์˜€๋‹ค. ํ•˜์ง€๋งŒ ์•ˆ ๊ทธ๋Ÿด ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์ด๋Ÿฐ ๋ถ€๋ถ„์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ์ ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. TCA์—์„œ๋Š” upstream ๋ฐฉ์‹์œผ๋กœ ์ด๋Ÿฌํ•œ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์œ„์— ์—ฐ๊ฒฐํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ๋‹ค.

๋‹จ ๊ทธ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ์•„๋ฌด๋Ÿฐ ๋™์ž‘๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด๋‹ค.

๋Œ์•„์™€์„œ

๊ณตํ†ต์˜ View๋Š” ์–ด๋–ค Screen์ด ๋“  ์–ด๋””์— ์œ„์น˜ํ•˜๋“  Back ๋ฒ„ํŠผ์˜ ์—ญํ• ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋””๋“  ๋ถ€์ฐฉ๋˜์–ด ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ, ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋’ค์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋กœ์ง ํ๋ฆ„์„ ์ฒ˜๋ฆฌํ•ด ์ค˜์•ผ ํ•˜์ง€๋งŒ ์ด๊ฑด ์˜ต์…˜์ด๋ฉฐ, View๋งŒ ์—ฐ๊ฒฐํ•œ๋‹ค.

Composableํ•จ์ˆ˜() {
    HStack/Column {
        BackButton() // << ์ด ๋ฒ„ํŠผ์—์„œ ์•Œ์•„์„œ ๋™์ž‘ํ•œ๋‹ค.
    }
}


TCA์—์„œ๋Š”?

TCA๋„ ์œ ์‚ฌํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆœ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹ค์Œ์˜ ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ•˜๋Š”๋ฐ, ๋„ˆ๋ฌด ์‰ฌ์šด ๋ถ€๋ถ„๋งŒ์„ ๊ฐ€์ ธ์™€์„œ ์„ค๋ช…ํ•˜๋‹ค ๋ณด๋‹ˆ ์„ค๋ช…์ด ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ƒ˜ํ”Œ ์ฝ”๋“œ๋Š” ๋‹จ์ˆœ ์ฐธ๊ณ ํ•ด ์ฃผ์‹œ๊ธธ

public struct BackButtonCellView: View {
  public var store: StoreOf<BackButtonCell>
  
  public init(store: StoreOf<BackButtonCell>) {
    self.store = store
  }
  
  public var body: some View {
    BackButton() // View
      .onTapGesture {
        viewStore.send(.back)
      }
  }
}

Reducer๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค. ์—ฌ๊ธฐ์—๋Š” State, Action์„ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค.

์ด Reducer๋Š” ํ™”๋ฉด์˜ ๋น„์ฆˆ๋‹ˆ์Šค ์ฒ˜๋ฆฌ์™€ ๋™์ž‘์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

public struct BackButtonCell: Reducer {
  public struct State: Equatable {
            
    public init() {
      // do nothing
    }
  }
  
  public enum Action: Equatable, ErrorAction {
    public enum Delegate: Equatable { // ์ƒ์œ„๋กœ ์ „๋‹ฌํ•  event
      case back
    }
    
    case back // ํ˜„์žฌ View์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ
  }
    
  public init() {}
  
  public var body: some ReducerOf<Self> {
    self.core
  }
  
  // ์œ„์—์„œ ์ •์˜ํ•œ event์— ๋Œ€ํ•œ ๋™์ž‘ ์ •์˜
  @ReducerBuilder<State, Action>
  var core: some ReducerOf<Self> {
    Reduce { state, action in
      switch action {
        case .back:
            return .run { send in
                await send(.delegate(.back))
            }

        case // ์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ด๋ฒคํŠธ๋“ค์€ ์—ฌ๊ธฐ์— ๋ชจ๋‘ ํฌํ•จ
          .delegate:
            return .none
      }
    }
  }
}

์—ฌ๊ธฐ๊นŒ์ง€๋Š” ๊ณตํ†ต์˜ View๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. Android์—์„œ Compose๋ฅผ ํ™œ์šฉํ•œ ์ด๋ฒคํŠธ ์ •์˜์™€ ์œ ์‚ฌํ•˜๋‹ค.


์ ์šฉํ•ด ๋ณด์ž

Android์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•˜์˜€๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒ ์ง€๋งŒ TCA์—์„œ๋Š” ์ด๋ฅผ ์ ์šฉํ•˜๋ ค๋ฉด ์ ์šฉํ•  View/Reducer์— ์ฝ”๋“œ๋ฅผ ๊ฐ๊ฐ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

๋จผ์ € ํฌํ•จํ•  View์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

IfLetStore(
self.store.scope(
    state: \.backButtonCell,
    action: Some.Action.backButtonCell
),
then: BackButtonCellView.init(store:)
)

Reducer์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํฌํ•จํ•œ๋‹ค.

public struct Some: Reducer {
    public struct State: Equatable {
        var backButtonCell: BackButtonCell.State? // ์‚ฌ์šฉํ•  State ์ •์˜

        public init() {
            self.backButtonCell = .init() // ๊ฐ์ฒด ์ƒ์„ฑ
        }
    }

    public enum Action: Equatable { // Action ์ •์˜
        case backButtonCell(BackButtonCell.Action)
    }

    // Cell ๊ฐ์ฒด ์ƒ์„ฑ
    public var body: some ReducerOf<Self> {
        self.core
            .ifLet(
                \State.backButtonCell,
                action: /Action.backButtonCell
            ) {
                BackButtonCell()
            }
    }

    // ์œ„์—์„œ ์ •์˜ํ•œ event์— ๋Œ€ํ•œ ๋™์ž‘ ์ •์˜
    @ReducerBuilder<State, Action>
    var core: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
                case .backButtonCell(.back): // ๋ฐ›์€ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ
                    return .none

                case .backButtonCell: // ๋‚˜๋จธ์ง€ ๋ชจ๋“  ์ด๋ฒคํŠธ๋Š” ์ƒ์œ„๋กœ ๋ณด๋‚ธ๋‹ค.
                    return .none
            }
        }
    }
}

๋งŒ์•ฝ ์œ„ ์ด๋ฒคํŠธ๋ฅผ ํ•ด๋‹น View์—์„œ ํ™œ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ReducerBuilder ์ •์˜๋ฅผ ์ˆ˜์ •ํ•œ๋‹ค.

// ์œ„์—์„œ ์ •์˜ํ•œ event์— ๋Œ€ํ•œ ๋™์ž‘ ์ •์˜
@ReducerBuilder<State, Action>
var core: some ReducerOf<Self> {
    Reduce { state, action in
        switch action {
            case .backButtonCell: // backButtonCell์— ๋Œ€ํ•œ ๋ชจ๋“  ์ด๋ฒคํŠธ return.
                return .none
        }
    }
}

๊ฐ„๋‹จํ•˜๊ฒŒ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ผ์„œ ์ดํ•ด๊ฐ€ ์–ด๋ ต์ง„ ์•Š์„ ๊ฒƒ ๊ฐ™๋‹ค.


์ด๋ฒคํŠธ์˜ ํ๋ฆ„

์•ž์„œ ์ž‘์„ฑํ•œ ํ•„์ž๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ์˜ Composable์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Back ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  • Back์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ์Šค์Šค๋กœ router ์ฒ˜๋ฆฌํ•œ๋‹ค.
    • ํ•„์š”ํ•˜๋‹ค๋ฉด ์ƒ์œ„๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์ด์ง€๋งŒ ํ•ต์‹ฌ์€, View๋งŒ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€์ด๋‹ค.

fun SomeScreen() {
    BackButton() // ์ด๋ ‡๊ฒŒ๋งŒ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?
}

ํ•˜์ง€๋งŒ TCA๋Š” view์™€ reducer๋ฅผ ๊ตฌ์„ฑํ•ด์•ผ ํ•˜๊ณ , ์ด ์ด๋ฒคํŠธ๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๊ฐ€ ์ž”๋œฉ ๋“ค์–ด๊ฐ€์•ผ ํ•œ๋‹ค.

ํŠน์ดํ•˜๊ฒŒ๋„ upstream ์ด๋ฒคํŠธ๋Š” A/B/C/D๋ผ๋Š” ์ˆœ์„œ๋Œ€๋กœ ํ™”๋ฉด์ด ์—ด๋ ธ๊ณ , D์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด A/B/C ๋ชจ๋‘๊ฐ€ ์ด ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ ๋ง์€ .backButtonCell ์ด๋ฒคํŠธ๋ฅผ ๋ชจ๋‘๊ฐ€ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ง์ด๋ฉฐ, ๋ถ„๋ฆฌ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

์–ด๋–ป๊ฒŒ ๋ณด๋ฉด ์žฅ์ ์ด์ง€๋งŒ ์ด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ƒ๊ฐ ์—†์ด ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ด์•ผ๊ธฐ๋‹ค.

๊ทธ๋Ÿผ ๊ทธ๋งŒํผ ๋ณต์žก๋„ ์—ญ์‹œ ๋งค์šฐ ๋†’์•„์งˆ ์ˆ˜ ์žˆ๋‹ค.


๋ˆ„๊ตฌ๋‚˜ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ํ๋ฆ„์ด๋ž€?

์—ฌ๊ธฐ์„œ ํ•œ ๋ฒˆ ๋” ์ƒ๊ฐํ•ด ๋ณผ ๋ถ€๋ถ„์€ ๋ˆ„๊ตฌ๋‚˜ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”๊ฐ€์ด๋‹ค.

A unidirectional data flow(UDF), ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ด์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค. TCA๊ฐ€ ์ข‹๊ธด ํ•˜์ง€๋งŒ UDF ์ ์œผ๋กœ ๋ณธ๋‹ค๋ฉด ๋„ˆ๋ฌด ์–ด๋ ค์›Œ์ง„๋‹ค๋Š” ๋‹จ์ ์ด๋‹ค. ์ด๋Ÿฐ ์ด์œ ๋กœ ์›น์—์„œ๋„ ๋ฆฌ๋•์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฑฐ ์•„๋‹๊นŒ ์‹ถ๋‹ค.

๊ฐœ๋ฐœ ํŽธ์˜์„ฑ ๋งŒ์„ ๋ณธ๋‹ค๋ฉด ์•Œ์•„์•ผ ํ•  ๊ฒƒ์ด ๋„ˆ๋ฌด ๋งŽ๋‹ค.

์ด๋Ÿฐ ์•„ํ‚คํ…์ฒ˜๋„ ์•Œ์•„์•ผ ํ•˜๊ณ , ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„๋„ ์ดํ•ดํ•ด์•ผ ํ•˜๊ณ , ์ด ํ๋ฆ„์˜ ๋์ด ์–ด๋”˜์ง€๋„ ์ฐพ์œผ๋ ค๋ฉด ํƒ€๊ณ  ํƒ€๊ณ  ์ด๊ฒŒ ์–ด๋””์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ  ์•„๋‹ˆ๊ธธ๋ž˜ ์™œ ์ด๋Ÿฐ ๋™์ž‘์„ ํ•  ๊ฒƒ์ธ๊ฐ€?์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์ด ๋งŽ์•„์ง„๋‹ค.

์„ค๊ณ„์ ์ธ ๊ด€์ ์—์„  ์žฌ์‚ฌ์šฉ ๊ตฌ์กฐ๋ฅผ ์ตœ๋Œ€ํ•œ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฒƒ์ด ๋‹น์—ฐํžˆ ์ข‹๋‹ค. ๊ทธ๋ž˜์•ผ ๊ฐœ๋ฐœ์—์„œ ํŽธํ•˜๊ธฐ ๋•Œ๋ฌธ์—.

์กฐ๋ฆฝ์‹ ์ง‘๋„ ์ง‘์ด๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๋ฆฝ์„ ์ž˜ํ•˜๋Š” ๊ฒƒ๋„ ๋งค์šฐ ์ข‹์€ ์„ค๊ณ„๋‹ค.

ํ•„์ž๋Š” ์–ด๋–ค ์•„ํ‚คํ…์ฒ˜๋„ ์ž˜ ๋งŒ๋“ค์–ด๋ด์•ผ ํŒฉํ† ๋ฆฌ ํŒจํ„ด์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

๊ทธ๋งŒํผ ํŒฉํ† ๋ฆฌ์ฒ˜๋Ÿผ ์ž˜ ๊ฐ€์ ธ๋‹ค ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ, TCA๋Š” ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์ด ์ƒ๊ธด๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

๊ฑฐ๊ธฐ์— ์—… ์ŠคํŠธ๋ฆผ(event)/๋‹ค์šด ์ŠคํŠธ๋ฆผ(data) ํ™œ์šฉ์„ ์ ์ ˆํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์‚ฐ์œผ๋กœ ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.


์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ํ™œ์šฉํ•ด ๋ณผ ๋ฒ•ํ•œ ๊ฒƒ์€?

ํ•„์ž๋Š” MVI ํŒจํ„ด์„ ์„ ํ˜ธํ•˜์ง€ ์•Š๋Š”๋‹ค. ์–ด์ฐจํ”ผ ๋ฆฌ์—‘ํŠธ์ด๊ณ , ๋ฆฌ์—‘ํŠธ์˜ ๊ฐœ๋…๋“ค์„ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ด๋‹ค.

๋งŒ์•ฝ MVI ํŒจํ„ด์˜ Reducer๊นŒ์ง€ ์ž˜ ์ ์šฉํ•˜์˜€๋‹ค๋ฉด MVI ๋ผ๊ธฐ๋ณด๋‹จ ๋ฆฌ์—‘ํŠธ๋ฅผ ์ž˜ ์ ๋ฆฝํ•œ ๊ฐœ๋…์ด๋ผ ์ƒ๊ฐํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„ MVI ํŒจํ„ด์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•˜๋ฉด ๋Œ€๋ถ€๋ถ„ ๋‹ค์Œ ์ •๋„๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๋‹ค.

  • Action๊ณผ State ์ ์šฉ
    • UiState ์ ์šฉ
    • Action view to event

์ด ๋‚ด์šฉ์€ ๊ตฌ๊ธ€ ์•ˆ๋“œ๋กœ์ด๋“œ ์•„ํ‚คํ…์ฒ˜์™€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š๋‹ค.

์ฐจ๋ผ๋ฆฌ circuit ๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ๋‹ค๋ฉด Reducer ๊ฐœ๋…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค. ์•„๋‹ˆ๋ฉด Flow๋ฅผ ํ™œ์šฉํ•ด Reducer ๊ฐœ๋…์„ ์ถ”๊ฐ€ํ•˜์—ฌ๋„ ์ข‹๋‹ค.


๋งˆ๋ฌด๋ฆฌ

ํ•„์ž๋Š” ๊ฐœ๋ฐœ์— ์žˆ์–ด์„œ ์ ์šฉ์ด ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•œ ๊ฑธ ์„ ํ˜ธํ•˜๋Š” ์ชฝ์„ ์„ ํƒํ•œ๋‹ค. ๋ฌด์ž‘์ • ๊ฐ„๊ฒฐํ•œ ๊ฒƒ์ด ์•„๋‹Œ ์ข€ ๋” ์‰ฝ๊ฒŒ ์ ์šฉํ•ด ๋ณผ ๋ฒ•ํ•œ ๊ฒƒ์„ ์„ ํ˜ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

TCA๋Š” ์•„์ง ๊ทธ ์ •๋„๋Š” ์•„๋‹ˆ๋ž€ ์ƒ๊ฐ์ด ๋“ ๋‹ค. ์›น์—์„œ๋Š” ๋ฆฌ๋•์Šค๋ฅผ ๋ณดํ†ต ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•˜๋Š”๋ฐ, ๊ตฌ์กฐ ์ž์ฒด๊ฐ€ ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ง€๋Š” ๊ฒฝํ–ฅ์ด ๊ฐ•ํ•ด ๋ณด์—ฌ์„œ์ธ๋“ฏํ•˜๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์— ์žˆ์–ด์„œ ๋”ฑํžˆ ์žฅ์ ์ด ๋ณด์ด์ง€ ์•Š๋Š” ๊ฒƒ๋„ ์ด์œ ์ด๊ธฐ๋„ ํ•œ๋ฐ, delegate๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํŽธํ•จ์€ ์žˆ์ง€๋งŒ ์ด ์ด๋ฒคํŠธ ํ†ต๋กœ๋Š” ์•ฑ์˜ ํ•˜๋‚˜๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค๋ฉด, ์‹ค์ˆ˜๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๊ณ , ์‹ค์ˆ˜๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•„๋„ ์ด์ƒํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ฒŒ ๋˜๋Š” ์ผ€์ด์Šค๋„ ๋ฐœ๊ฒฌ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์ปค ๋ณด์ธ๋‹ค.

๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ๋””๋ฒ„๊น…์ด ํŽธํ• ๊นŒ? EventBus์˜ ๋””๋ฒ„๊น…์ด ์–ด๋ ค์› ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ TCA ์—ญ์‹œ ๋น„์Šทํ•œ ๋Š๋‚Œ์„ ๋ฐ›์•˜๋‹ค.

๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด๋„ ์žฌ๋ฏธ์žˆ๋Š” ์•„ํ‚คํ…์ฒ˜์ž„์€ ํ‹€๋ฆผ์—†๊ณ , UDF๋ฅผ ์ž˜ ๊ตฌ์„ฑํ•˜์—ฌ ๊ณ ๋ฏผํ•ด ๋ณธ๋‹ค๋ฉด ์ถฉ๋ถ„ํžˆ ์ข‹์€? ์•„ํ‚คํ…์ฒ˜๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ƒ๊ฐ๋ณด๋‹ค ๋งค์šฐ ์žฌ๋ฏธ์žˆ์—ˆ๊ธฐ์— ์ด๋Ÿฐ ๊ธ€๋„ ์ž‘์„ฑํ•ด ๋ณธ๋‹ค.



About Taehwan

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments