iOS/TCA

[SwiftUI] μƒνƒœκ΄€λ¦¬(redux)

year.number 2023. 7. 6. 17:30

 

 

 

 

 

PointFree κ°•μ˜ Composable Architecture
Composable State Management: Reducers 정리

 

 

 

πŸ“š 이번 κ°•μ˜ λͺ©ν‘œ

 

μ €λ²ˆ κ²Œμ‹œκΈ€μ—μ„œ μž‘μ„±ν•œ μ½”λ“œμ—μ„œ 

 

1. CombineμœΌλ‘œλΆ€ν„° 뢄리
2. κ°’ νƒ€μž…μ˜ 이점 ν™œμš©

 

을 μœ„ν•΄ μƒνƒœ λ³€ν™”λ₯Ό κ°μ§€ν•˜λŠ” 객체λ₯Ό λ§Œλ“€κ³ μž ν•œλ‹€!

 

 

 

 

 

πŸ“š μ½”λ“œ

 

struct AppState {
    var count = 0
    var favoritePrimes: [Int] = []
    var loggedInUser: User?
    var activityFeed: [Activity] = []
    
    var didChange = PassthroughSubject<Void, Never>()
    
    struct Activity {
        let timestamp: Date
        let type: ActivityType
        
        enum ActivityType {
            case addedFavoritePrime(Int)
            case removedFavoritePrime(Int)
        }
    }
    
    struct User {
        let id: Int
        let name: String
        let bio: String
    }
}

 

κΈ°μ‘΄ class νƒ€μž…μ˜ AppState μ½”λ“œλ₯Ό ꡬ쑰체둜 λ³€κ²½ν•œλ‹€. 이러면 더이상 @Published둜 λ³€μˆ˜λ₯Ό μ„ μ–Έν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.

여기에 ObservableObjectλ₯Ό μ€€μˆ˜ν•˜κΈ° μœ„ν•΄μ„œ ꡬ쑰체λ₯Ό κ°μ‹ΈλŠ” 클래슀λ₯Ό μƒˆλ‘œ 선언해야함(Store)

 

 

 

 

 

 

 

 

final class Store<Value, Action>: ObservableObject {
    let reducer: (inout Value, Action) -> Void
    @Published var value: Value
    
    init(initialValue: Value, reducer: @escaping (inout Value, Action) -> Void) {
      self.value = initialValue
      self.reducer = reducer
    }
    
    func send(_ action: Action) {
        self.reducer(&self.value, action)
    }
}

 

AppState의 λ³€ν™”λ₯Ό κ°μ§€ν•˜λŠ” Store<AppState> 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•œ 클래슀λ₯Ό μ •μ˜ν•œλ‹€.

 

Store 클래슀의 μ—­ν• : κ°’ μœ ν˜•μ„ λž˜ν•‘ν•΄μ„œ κ΄€μ°°μžμ—κ²Œ ν›… 제곡
AppState에 λŒ€ν•΄μ„œλŠ” μ•Œ ν•„μš”κ°€ μ—†κΈ° 떄문에 μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ μ„ μ–Έ

 

 

 

 

 

 

 

// μ‚¬μš©μž μ•‘μ…˜ νƒ€μž… μ§€μ •
enum CounterAction {
    case decrementTapped
    case incrementTapped
}
enum PrimeModalAction {
    case saveFavoritePrimeTapped
    case removeFavoritePrimeTapped
}
enum FavoritePrimesAction {
    case deleteFavoritePrimes(IndexSet)
}
enum AppAction {
    case counter(CounterAction)
    case primeModal(PrimeModalAction)
    case favoritePrimes(FavoritePrimesAction)
}


func appReducer(state: inout AppState, action: AppAction) -> Void {
    switch action {
    case .counter(.decrementTapped):
        state.count -= 1
    case .counter(.incrementTapped):
        state.count += 1
    case .primeModal(.saveFavoritePrimeTapped):
        state.favoritePrimes.append(state.count)
        state.activityFeed.append(.init(timestamp: Date(), type: .addedFavoritePrime(state.count)))
    case .primeModal(.removeFavoritePrimeTapped):
        state.favoritePrimes.removeAll(where: { $0 == state.count })
        state.activityFeed.append(.init(timestamp: Date(), type: .removedFavoritePrime(state.count)))
    case let .favoritePrimes(.deleteFavoritePrimes(indexSet)):
        for index in indexSet {
          let prime = state.favoritePrimes[index]
          state.favoritePrimes.remove(at: index)
          state.activityFeed.append(.init(timestamp: Date(), type: .removedFavoritePrime(prime)))
        }
    }
}

 

μ‚¬μš©μž μ•‘μ…˜ νƒ€μž…κ³Ό 그에 λ”°λ₯Έ μ•‘μ…˜μ„ μ •μ˜ν•œλ‹€

 

 

 

 

 

 

 

 

@ObservedObject var store: Store<AppState, AppAction>

 

ObservedObject λ³€μˆ˜λ₯Ό λ‹€μŒκ³Ό 같이 λ³€κ²½ν•΄μ€€λ‹€

 

 

 

 

 

 

 

 

 

 

μƒνƒœ 변화에 λ”°λ₯Έ 행동을 reducer에 κ²©λ¦¬ν•΄μ„œ storeμ—μ„œ send() λ©”μ„œλ“œ ν˜ΈμΆœμ„ 톡해 ν•  수 μžˆλ„λ‘ ν•œλ‹€.