Notice
Recent Posts
Recent Comments
Link
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

[TCA] ๊ณต๋ถ€๊ธฐ๋ก #1 (ReducerProtocol) ๋ณธ๋ฌธ

apple/๐Ÿฆฅ TCA

[TCA] ๊ณต๋ถ€๊ธฐ๋ก #1 (ReducerProtocol)

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2023. 1. 16. 18:31

#### TCA ๊ณต๋ถ€์‹œ์ž‘ 

 

 

(์ฒซ ํฌ์ŠคํŒ… ๋‚ ์งœ) 2023. 1. 16. 18:31
(์—…๋ฐ์ดํŠธ) 2023. 10. 08. 01:08 
 - ์—…๋ฐ์ดํŠธ ์‚ฌ์œ : TCA 1.0.0 ์ถœ์‹œ๋กœ ์ธํ•œ ๊ณต๋ถ€ ๊ณ„ํš ์—…๋ฐ์ดํŠธ. ํ•ด๋‹น ํฌ์ŠคํŒ…์€ ํ˜„์žฌ ํฌ๊ฒŒ ์˜๋ฏธ๊ฐ€ ์—†๋Š” ์ƒํƒœ
 - ํ™˜๊ฒฝ
   - Xcode 15.0
   - TCA 1.0.0
 - TCA ๊ณต๋ถ€๋Š” ์•„๋ž˜ ๋งํฌ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ
https://rldd.tistory.com/category/apple/%F0%9F%A6%A5%20TCA

 

'apple/๐Ÿฆฅ TCA' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ธ€ ๋ชฉ๋ก

 

rldd.tistory.com

 

 

 

 

https://github.com/pointfreeco/swift-composable-architecture

 

GitHub - pointfreeco/swift-composable-architecture: A library for building applications in a consistent and understandable way,

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind. - GitHub - pointfreeco/swift-composable-architecture: A library for bu...

github.com

 

 

#### ํ™˜๊ฒฝ

 - XCode: 14.0

 - swift-composable-architecture ~> main

 

 

#### ์–ด๋–ป๊ฒŒ ํ•™์Šตํ•  ๊ฒƒ์ธ๊ฐ€?

 - TCA์˜ Tic-Tac-Toc Example์„ ๋ถ„์„ํ•ด๋ณด๋ฉด ํ† ์ด ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๊ธฐ.

 

### ๋ชฉํ‘œ

 - TCA์˜ ๋งŽ์€ ๋ถ€๋ถ„์„ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

 

### ์ž‘์—… ํ›„ ๊ฐœ์ธ์ ์ธ ์ •๋ฆฌ

 - IfLetStore: nilํƒ€์ž…์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์—ฌ nil ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์™€ SwiftUI์— View๋ฅผ ๋” ๊น”๋”ํ•˜๊ฒŒ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•ด ๋ณด์ž„.

 

 - Scope: onAppear๊ฐ€ ์ค‘๋ณต ํ˜ธ์ถœ๋˜๋Š” ์ด์Šˆ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, Scope๋ฅผ ํ†ตํ•ด ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•˜๊ณ  ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Œ.

 

 - View(Action, State), Core(Action, State)๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ํŒ๋‹จ. 

 : ReactorKit์„ ์ƒ๊ฐํ•˜๋ฉด ๋™์ผํ•œ๊ฒŒ ์•„๋‹๊นŒ๋ฅผ ๊ณ ๋ฏผํ–ˆ์œผ๋‚˜, ๋‘˜์ด ๋‚˜๋‰จ์œผ๋กœ์จ Core์˜ Action์ด ๋” ๋ช…ํ™•ํ•˜๊ณ  ๊น”๋”ํ•ด์ง„ ๊ธฐ๋ถ„์ด ๋“ค์—ˆ์Œ.

 : ์ด ๋ฐฉ์‹์ด ์ •๋ง ์ข‹๋‹ค๊ณ  ๋Š๋ผ๋Š”๋ฐ, ์ฝ”๋“œ ๊ด€๋ฆฌ๊ฐ€ ๋” ์ž˜ ๋˜๋Š” ๊ธฐ๋ถ„์ด๋‹ค!

 

 - TCA์˜ ViewModel ๋Œ€์‹ ์— Core๋ผ๋Š” ๋„ค์ด๋ฐ์œผ๋กœ ์ž‘์„ฑํ•˜๊ธฐ.

 

 - Effect๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ, Core์˜ Action์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Effect.Action์„ ํ†ตํ•ด ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๊ธฐ๋Šฅ ์—ญํ• ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ

 

 - @Dependency ์‚ฌ์šฉ

 : DependencyKey๋ฅผ ์ƒ์†๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋ฉฐ, DependencyValue๋ฅผ ํ†ตํ•ด์„œ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Œ.

  -> ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์— ์ด์‹ํ•˜๋ฉด์„œ ๋ฐœ๊ฒฌํ•œ ๋ฌธ์ œ์  ๋ฐ ๊ณ ๋ฏผ(DIP๊ฐ€ ์ด๋ž˜์„œ ํ•„์š”ํ•œ๊ฑฐ๊ตฌ๋‚˜๋ฅผ ๋Š๋‚Œ)

 : SDK์—์„œ Network์™€ ๋กœ์ปฌ DB๋ฅผ ๋“ค๊ณ  ์žˆ์Œ.

 : @Dependency๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ get-only์œผ๋กœ ๋‚˜ํƒ€๋‚จ. (ํ™•์‹คํ•˜๊ฒŒ get-only์ธ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์Œ set์œผ๋กœ ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ, ์ด๊ฑด ์กฐ์‚ฌ๊ธฐ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.)

 

 - pullback์„ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

 : SwiftUI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋ทฐ์˜ ๊ณ„์ธต์ด ๊นŠ์–ด์ง€๋ฉด ๋‚˜๋ˆ„๊ณคํ•˜๋Š”๋ฐ, ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๊ฐ€ ์•„์ง ๋ฏธ์ˆ™ํ•ด์„œ ํ•˜๋‚˜์˜ ํŒŒ์ผ์— ๋•Œ๋ ค๋ฐ•์€ ๋Š๋‚Œ์ด ์—†์ง€์•Š์•„ ์žˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ• ์ง€ ๊ผญ ๊ณ ๋ฏผํ•ด๋ณด๊ธฐ

 

@Depedency(\.sdkService) var sdkService

// โœ… ๋„คํŠธ์›Œํฌ ๊ด€๋ จ ๋น„๋™๊ธฐ ์ž‘์—…์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ด ์‚ฌ์šฉ๊ฐ€๋Šฅ
sdkService.fetchAllItem
	.catchToEffect(Action.fetchAllItemSuccess)
    // ํ›„๋žต
    
    
// ๐Ÿšจ ๋กœ์ปฌ DB์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋ฉด?
sdkService.location = location // get-only error ๋ฐœ์ƒ

 

 

### ๋‹ค์Œ ๋ชฉํ‘œ

 - TCACoordinator๋ฅผ ์ ์šฉํ•˜์—ฌ ํ™”๋ฉด 3๊ฐœ์ด์ƒ์˜ ํ™”๋ฉด์ „ํ™˜์„ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ.

 

#### ์‹œ์ž‘ํ•˜๊ธฐ ์ „ ์œ ์˜์‚ฌํ•ญ

 

https://github.com/pointfreeco/swift-composable-architecture/discussions/1477

 

Road to 1.0 · Discussion #1477 · pointfreeco/swift-composable-architecture

It’s been almost 2 and a half years since we first released the Composable Architecture, so we’ve been getting more and more questions about its 0.x.x prerelease versioning, what’s holding up a 1.0...

github.com

 

# Removing the old Reducer struct

โœ…  ReducerProtocol(Reducer) ์‚ฌ์šฉํ•ด๋ผ. 

  - 1.0 ๋ฆด๋ฆฌ์ฆˆ ๋‹ค๊ฐ€์˜ค๋ฉด AnyReducer fully deprecated(์™„์ „ ์ค‘๋‹จ) ํ• ๊ฑฐ๋ผ๊ณ  ํ•จ.

  - ํ˜„์žฌ๋Š” Deprecated ๋˜์—ˆ๋‹ค๋Š” ๋…ธ๋ž€์ƒ‰ ๋ฌธ๊ตฌ๊ฐ€ ๋‚˜ํƒ€๋‚จ.

 

โœ… ๋ณ€๊ฒฝ ๋‚ด์šฉ

  - typealias Reducer = AnyReducer๋Š” ์‚ญ์ œ ์˜ˆ์ • (0.43.0) ๋ฒ„์ „์—์„œ ์ „์—ญ์œผ๋กœ Reducer๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ.

  - ReducerProtocol์˜ ์ด๋ฆ„์„ Reducer๋กœ ๋ณ€๊ฒฝ

  - Introducing a hard-deprecated typealias ReducerProtocol = Reducer

   : ์„ธ๋ฒˆ์งธ๋Š” ์ •ํ™•ํ•˜๊ฒŒ ์˜๋ฏธ๋Š” ๋ชจ๋ฅด๊ฒ ๋Š”๋ฐ, ์•„๋ž˜ ์˜์–ด๋ฅผ ์ฐธ๊ณ ํ•˜์ž๋ฉด Reducer๋กœ ํ”„๋กœํ† ์ฝœ ์ด๋ฆ„ ์ ์šฉํ•˜๋ž€ ์˜๋ฏธ์ธ๋“ฏ

 

If you have already updated your code base to 0.43.0, then none of these changes are breaking. You will just have to rename occurrences of ReducerProtocol to Reducer, which Xcode should be able to help with.

 

# Removing many Combine-specific features of Effect

Concurrency ๋ฆด๋ฆฌ์ฆˆํ•˜๋ฉด์„œ (.run, .task, .fireAndForgot)์„ ์‚ฌ์šฉํ•˜๊ณ  3๊ฐ€์ง€ ์ง„์ž…์ ์— ๋งž์ถ”์–ด์„œ ์œ ํ˜•์„ ๋‹จ์ˆœํ™”.

๋”ฐ๋ผ์„œ ๋ถˆํ•„์š”ํ•œ ๋ ˆ๊ฑฐ์‹œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ ์ž ํ•จ.

 

Effect์˜ ๊ฒฝ์šฐ์—๋Š” Combine publisher๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋”์ด์ƒ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ช‡๋ช‡ ๋ถ€๋ถ„์—์„œ ์ œ๊ฑฐํ•˜๊ธฐ๋กœ ํ•จ.

์˜ˆ๋ฅผ ๋“ค์—ฌ์„œ Publisher Protocol์—์„œ Failure๋กœ ํ–ˆ์ง€๋งŒ reducer์•ˆ์—์„œ Never๋กœ ์ž ๊ฒจ์žˆ๋Š” ๋“ฑ.

 

๊ทธ๋Ÿฌ๋‹ˆ๊นŒ Failure๋Š” ๋”์ด์ƒ ์˜๋ฏœ๊ฐ€ ์—†์œผ๋ฏ€๋กœ, Effect<Action, Failure> -> Effect<Action>์œผ๋กœ ๋‹จ์ˆœํ™”ํ•˜๊ณ ์ž ํ•จ.

 

 

 -  Effect ์ œ๊ฑฐํ• ๊ฑฐ์•ผ.

 -  EffectTask -> Effect๋กœ ์ด๋ฆ„ ๋ฐ”๊ฟ€๊ฑฐ์•ผ

 - ๊ธฐ์กด EffectTask๋Š” ๊ณ„์† ์ž‘๋™ํ•˜๋„๋ก ์ง€์›ํ• ๊ฑฐ์ง€๋งŒ Xcode์—์„œ Deprecated๋œ๊ฑฐ ์•Œ๋ ค์ฃผ์–ด์„œ ๋„ˆ๊ฐ€ ๋ณ€๊ฒฝํ•˜๋„๋ก ๋„์šธ๊ฑฐ์•ผ.

 

 

 

#### UI ๊ฒฐ๊ณผ๋ฌผ

๊ฒฐ๊ณผ๋ฌผ UI.gif

 

 

#### ์ฝ”๋“œ

 

//  LocationSetting.swift

//
//  LocationSetting.swift
//  UJeongApp
//
//  Created by Hamlit Jason on 2023/01/03.


import SwiftUI
import ComposableArchitecture

// MARK: - LocationSettingView

public struct LocationSettingView: View {
    typealias Core = LocationSettingCore
    
    private let store: StoreOf<Core>
    @Environment(\.dismiss) private var dismiss
    
    struct ViewState: Equatable {
        let allLocationSection = LocationSection.allSection()
        public var selectedLocation: String
        
        init(state: Core.State) {
            self.selectedLocation = state.selectedLocation
        }
    }
    
    enum ViewAction {
        case itemSelected(location: String)
        case onAppear
    }
    
    init(store: StoreOf<Core>) {
        self.store = store
    }
    
    let columns = [GridItem(.flexible()),
                   GridItem(.flexible()),
                   GridItem(.flexible())]
    
    public var body: some View {
        WithViewStore(
            self.store,
            observe: ViewState.init,
            send: Core.Action.init
        ) { viewStore in
            NavigationView {
                
                ForEach(viewStore.state.allLocationSection) { section in
                    List {
                        Text("๋‚ด๊ฐ€ ์„ ํƒํ•œ ์ง€์—ญ:  \(viewStore.selectedLocation)")
                        
                        Section {
                            LazyVGrid(
                                columns: columns,
                                alignment: .center,
                                spacing: .none
                            ) {
                                ForEach(section.location.districts, id: \.self) { location in
                                    
                                    Text(location)
                                        .padding(.horizontal, 5)
                                        .padding(.vertical, 5)
                                        .overlay {
                                            if location == viewStore.state.selectedLocation {
                                                RoundedRectangle(cornerRadius: 10)
                                                    .stroke(Color.black, lineWidth: 3)
                                            }
                                        }
                                        .onTapGesture {
                                            viewStore.send(.itemSelected(location: location))
                                        }
                                }
                            }
                        } header: {
                            Text(section.location.city)
                        }
                    }
                    .listStyle(.sidebar)
                }
                
                
                .onAppear {
                    viewStore.send(.onAppear)
                }
                .navigationBarTitle("์ง€์—ญ ์„ ํƒ", displayMode: .inline)
                .toolbar {
                    Button {
                        dismiss()
                    } label: {
                        Image(systemName: "checkmark")
                    }
                }
            }
        }
    }
}

 

//  LocationSettingCore.swift

//
//  LocationSettingCore.swift
//  UJeongApp
//
//  Created by Hamlit Jason on 2023/01/16.
//

import Foundation
import ComposableArchitecture

public struct LocationSettingCore: ReducerProtocol {
    public struct State: Equatable {
        var selectedLocation: String = ""
    }
    
    public enum Action {
        case onAppear
        case itemSelected(location: String)
        case updateAppStorage
        
        init(action: LocationSettingView.ViewAction) {
            switch action {
            case .onAppear:
                self = .onAppear
            case let .itemSelected(location):
                self = .itemSelected(location: location)
            }
        }
    }
    
    // NOTE: - func reduce๋ž‘ ๋™์ผ
    public var body: some ReducerProtocol<State, Action> {
        Reduce { state, action in
            switch action {
            case .onAppear:
                state.selectedLocation = sdkService.selectedLocation
                
                return .none
            case let .itemSelected(location):
                state.selectedLocation = location
                
                return EffectTask<Action>(value: .updateAppStorage)
            case .updateAppStorage:
                sdkService.selectedLocation = state.selectedLocation
                
                return .none
            }
        }
    }
    
    private let sdkService = UJeongSDKService()
}

 

// LocationSection.Swift

 

//
//  LocationSection.swift
//  UJeongApp
//
//  Created by Hamlit Jason on 2023/01/16.
//

import Foundation

struct LocationSection: Identifiable, Equatable {
    let id = UUID()
    
    let location: Location
    
    static func allSection() -> [LocationSection] {
        let seoul = LocationSection(location: .init(city: "์„œ์šธํŠน๋ณ„์‹œ",
                                                    districts: Gu.allCases.map { $0.rawValue }.sorted(by: <)))
        
        return [seoul]
    }
}

 

// Gu.Swift

//
//  Location.swift
//  UJeongApp
//
//  Created by Hamlit Jason on 2023/01/04.
//

import Foundation

/*
 ๋Œ€๋„์‹œ๋Š” ๊ตฌ๋กœ ๋ถ„๋ฅ˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ตฌ๋‹จ์œ„๋กœ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค.
 
 ๋งŒ์•ฝ ์ง€๋ฐฉ ์†Œ๋„์‹œ์˜ ๊ฒฝ์šฐ์—๋Š” ๊ตฌ๋‹จ์œ„๋กœ ๋ถ„๋ฅ˜ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค๋ฉด ์‹œ๋‹จ์œ„๋กœ ๋ถ„๋ฅ˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 ์ฐจํ›„ ํ™•์žฅ์„ฑ์„ ๋Š˜ ์ƒ๊ฐํ•ฉ์‹œ๋‹ค.
 
 */

public struct Location: Hashable {
    public var city: String // ๋„์‹œ์ด๋ฆ„
    public var districts: [String] // ์ง€์—ญ๊ตฌ
}

public enum Gu: String, CaseIterable {
    case ๊ฐ•์„œ๊ตฌ
    case ์–‘์ฒœ๊ตฌ
    case ๊ตฌ๋กœ๊ตฌ
    case ๊ธˆ์ฒœ๊ตฌ
    case ๊ด€์•…๊ตฌ
    
    case ์˜๋“ฑํฌ๊ตฌ
    case ๋™์ž‘๊ตฌ
    case ์„œ์ดˆ๊ตฌ
    case ๊ฐ•๋‚จ๊ตฌ
    case ์†กํŒŒ๊ตฌ
    
    case ๊ฐ•๋™๊ตฌ
    case ์šฉ์‚ฐ๊ตฌ
    case ์„ฑ๋™๊ตฌ
    case ๊ด‘์ง„๊ตฌ
    case ๋™๋Œ€๋ฌธ๊ตฌ
    
    case ์ค‘๋ž‘๊ตฌ
    case ๋…ธ์›๊ตฌ
    case ๋„๋ด‰๊ตฌ
    case ๊ฐ•๋ถ๊ตฌ
    case ์„ฑ๋ถ๊ตฌ
    
    case ์ข…๋กœ๊ตฌ
    case ์„œ๋Œ€๋ฌธ๊ตฌ
    case ์ค‘๊ตฌ
    case ๋งˆํฌ๊ตฌ
    case ์€ํ‰๊ตฌ
}

'apple > ๐Ÿฆฅ TCA' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[TCA] SharedState  (1) 2023.09.27
[TCA] OptionalState (IfLetCase)  (0) 2023.09.27
[TCA] FocusState  (0) 2023.09.27
[TCA] Binding  (0) 2023.09.27
[TCA] Tutorial #5 (Multiple presentation destinations)  (0) 2023.09.24
Comments