SwiftUI์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ. 1๋ถ€: Dataflow ๋ฐ Redux

SwiftUI์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ. 1๋ถ€: Dataflow ๋ฐ Redux

WWDC 2019์˜ State of the Union ์„ธ์…˜์— ์ฐธ์„ํ•œ ํ›„ ์ €๋Š” SwiftUI์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ์ด ์ž‘์—…์— ๋งŽ์€ ์‹œ๊ฐ„์„ ๋ณด๋ƒˆ๊ณ  ์ด์ œ ๊ด‘๋ฒ”์œ„ํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์œ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์ œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ €๋Š” ์ด๊ฒƒ์„ MovieSwiftUI๋ผ๊ณ  ๋ถˆ๋ €์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ƒˆ ์˜ํ™”์™€ ์˜ค๋ž˜๋œ ์˜ํ™”๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปฌ๋ ‰์…˜์œผ๋กœ ์ˆ˜์ง‘ํ•˜๋Š” ์•ฑ์ž…๋‹ˆ๋‹ค. TMDB API. ๋‚˜๋Š” ํ•ญ์ƒ ์˜ํ™”๋ฅผ ์‚ฌ๋ž‘ํ–ˆ๊ณ , ์˜ค๋ž˜์ „๋ถ€ํ„ฐ ์ด ๋ถ„์•ผ์—์„œ ์ผํ•˜๋Š” ํšŒ์‚ฌ๋ฅผ ์„ค๋ฆฝํ•˜๊ธฐ๋„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ํšŒ์‚ฌ๋Š” ๋ฉ‹์ง€๋‹ค๊ณ  ํ•  ์ˆ˜ ์—†์ง€๋งŒ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ํ›Œ๋ฅญํ–ˆ์Šต๋‹ˆ๋‹ค!

์•Œ๋ฆผ: "Habr"์˜ ๋ชจ๋“  ๋…์ž๋ฅผ ์œ„ํ•œ - "Habr" ํ”„๋กœ๋ชจ์…˜ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Skillbox ๊ณผ์ •์— ๋“ฑ๋กํ•  ๋•Œ 10 ๋ฃจ๋ธ” ํ• ์ธ.

Skillbox๋Š” ๋‹ค์Œ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ต์œก์šฉ ์˜จ๋ผ์ธ ๊ณผ์ • "์ง์—… ์ž๋ฐ” ๊ฐœ๋ฐœ์ž".

๊ทธ๋ ‡๋‹ค๋ฉด MovieSwiftUI๋Š” ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?

  • API์™€ ์ƒํ˜ธ์ž‘์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฑฐ์˜ ๋ชจ๋“  ์ตœ์‹  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ด๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์š”์ฒญ ์‹œ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๊ณ  ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ JSON์„ Swift ๋ชจ๋ธ๋กœ ๊ตฌ๋ฌธ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋”ฉ ๊ฐ€๋Šฅ.
  • ์š”์ฒญ ์‹œ ๋กœ๋“œ๋œ ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ์บ์‹œํ•ฉ๋‹ˆ๋‹ค.
  • iOS, iPadOS, macOS์šฉ ์ด ์•ฑ์€ ์ด๋Ÿฌํ•œ OS ์‚ฌ์šฉ์ž์—๊ฒŒ ์ตœ๊ณ ์˜ UX๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ž์‹ ๋งŒ์˜ ์˜ํ™” ๋ชฉ๋ก์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ทฐ, ๊ตฌ์„ฑ ์š”์†Œ ๋ฐ ๋ชจ๋ธ์€ Redux ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์€ ๋‹จ๋ฐฉํ–ฅ์ž…๋‹ˆ๋‹ค. ์™„์ „ํžˆ ์บ์‹œ, ๋ณต์› ๋ฐ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal ๋“ฑ์˜ ๊ธฐ๋ณธ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ๋ณด๊ธฐ, ์ œ์Šค์ฒ˜, UI/UX๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

SwiftUI์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ. 1๋ถ€: Dataflow ๋ฐ Redux
์‚ฌ์‹ค ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ๋งค๋„๋Ÿฌ์› ๊ณ , GIF๋Š” ์ข€ ์—‰๋šฑํ•˜๊ฒŒ ๋‚˜์™”๋„ค์š”.

์•ฑ ์ž‘์—…์„ ํ•˜๋ฉด์„œ ๋งŽ์€ ๊ฒฝํ—˜์„ ํ•˜๊ฒŒ ๋˜์—ˆ๊ณ  ์ „๋ฐ˜์ ์œผ๋กœ ๊ธ์ •์ ์ธ ๊ฒฝํ—˜์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , 13์›”์— ์ด๋ฅผ ๊ฐœ์„ ํ•˜์—ฌ iOS XNUMX ์ถœ์‹œ์™€ ๋™์‹œ์— AppStore์— ๊ฒŒ์‹œํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

Redux, BindableObject ๋ฐ EnvironmentObject

SwiftUI์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ. 1๋ถ€: Dataflow ๋ฐ Redux

๋‚˜๋Š” ์•ฝ XNUMX๋…„ ๋™์•ˆ Redux๋ฅผ ์‚ฌ์šฉํ•ด์™”๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋Œ€์ ์œผ๋กœ Redux์— ๋Œ€ํ•ด ์ž˜ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์ €๋Š” ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜์‘ ์›น์‚ฌ์ดํŠธ๋Š” ๋ฌผ๋ก  ๊ธฐ๋ณธ iOS(Swift) ๋ฐ Android(Kotlin) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์—๋„ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ €๋Š” SwiftUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์ถ•์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์•„ํ‚คํ…์ฒ˜๋กœ Redux๋ฅผ ์„ ํƒํ•œ ๊ฒƒ์„ ๊ฒฐ์ฝ” ํ›„ํšŒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. UIKit ์•ฑ์—์„œ Redux๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ€์žฅ ์–ด๋ ค์šด ๋ถ€๋ถ„์€ ์Šคํ† ์–ด์™€ ์ž‘์—…ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๊ฒ€์ƒ‰ํ•˜์—ฌ ๋ทฐ/์ปดํฌ๋„ŒํŠธ์— ๋งคํ•‘ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ReSwift ๋ฐ ReKotlin์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ์ข…์˜ ์ปค๋„ฅํ„ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๊ฝค ๋งŽ์Šต๋‹ˆ๋‹ค. ๋ถˆํ–‰ํžˆ๋„ ์•„์ง์€ ์˜คํ”ˆ ์†Œ์Šค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

์ข‹์€ ์†Œ์‹! Redux๋ฅผ ์‚ฌ์šฉํ•  ๊ณ„ํš์ด๋ผ๋ฉด SwiftUI์—์„œ ๊ฑฑ์ •ํ•ด์•ผ ํ•  ์œ ์ผํ•œ ๊ฒƒ์€ ์ƒ์ , ์ƒํƒœ ๋ฐ ๋ฆฌ๋“€์„œ์ž…๋‹ˆ๋‹ค. @EnvironmentObject ๋•๋ถ„์— SwiftUI๊ฐ€ ์ƒ์ ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ €์žฅ์†Œ๋Š” BindableObject๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ Swift ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. SwiftUIFlux, Redux์˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ œ ๊ฒฝ์šฐ์—๋Š” MovieSwiftUI์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. ๋‚˜๋„ ๋‹จ๊ณ„๋ณ„ ํŠœํ† ๋ฆฌ์–ผ์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค, ์ด ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ?

final public class Store<State: FluxState>: BindableObject {
    public let willChange = PassthroughSubject<Void, Never>()
        
    private(set) public var state: State
    
    private func _dispatch(action: Action) {
        willChange.send()
        state = reducer(state, action)
    }
}

์ž‘์—…์„ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๊ธฐ์–ด๋ฐ•์Šค๊ฐ€ ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ˜„์žฌ ์ƒํƒœ์— ๋”ฐ๋ผ ์ž‘์—…์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ž‘์—… ์œ ํ˜• ๋ฐ ๋ฐ์ดํ„ฐ์— ๋”ฐ๋ผ ์ˆ˜์ •๋œ ์ƒˆ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

store๋Š” BindableObject์ด๊ธฐ ๋•Œ๋ฌธ์— PassthroughSubject์—์„œ ์ œ๊ณตํ•˜๋Š” willChange ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด SwiftUI์— ์•Œ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋Š” BindableObject๊ฐ€ PublisherType์„ ์ œ๊ณตํ•ด์•ผ ํ•˜์ง€๋งŒ ํ”„๋กœํ† ์ฝœ ๊ตฌํ˜„์ด ์ด๋ฅผ ๊ด€๋ฆฌํ•  ์ฑ…์ž„์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ „๋ฐ˜์ ์œผ๋กœ ์ด๊ฒƒ์€ Apple์˜ ๋งค์šฐ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ ๋ Œ๋”๋ง ์ฃผ๊ธฐ์—์„œ SwiftUI๋Š” ์ƒํƒœ ๋ณ€๊ฒฝ์— ๋”ฐ๋ผ ๋ทฐ์˜ ๋ณธ๋ฌธ์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์ด๊ฒƒ์ด SwiftUI์˜ ํ•ต์‹ฌ์ด์ž ๋งˆ๋ฒ•์ž…๋‹ˆ๋‹ค. ์ด์ œ ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ๋ทฐ์—์„œ๋Š” ์ƒํƒœ์—์„œ ์ˆ˜์‹ ๋œ ๋ฐ์ดํ„ฐ์™€ ๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์— ๋”ฐ๋ผ ๋ทฐ๊ฐ€ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 
    var window: UIWindow?
 
 
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            let controller = UIHostingController(rootView: HomeView().environmentObject(store))
            window.rootViewController = controller
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
 
struct CustomListCoverRow : View {
    @EnvironmentObject var store: Store<AppState>
    
    let movieId: Int
    var movie: Movie! {
        return store.state.moviesState.movies[movieId]
    }
    
    var body: some View {
        HStack(alignment: .center, spacing: 0) {
            Image(movie.poster)
        }.listRowInsets(EdgeInsets())
    }
}

Store๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ๋•Œ EnvironmentObject๋กœ ์ฃผ์ž…๋˜๊ณ  @EnvironmentObject๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ๋ทฐ์—์„œ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŒŒ์ƒ ์†์„ฑ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์—์„œ ๋น ๋ฅด๊ฒŒ ๊ฒ€์ƒ‰๋˜๊ฑฐ๋‚˜ ๊ณ„์‚ฐ๋˜๋ฏ€๋กœ ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์ฝ”๋“œ๋Š” ์˜ํ™” ํฌ์Šคํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ด๋ฏธ์ง€๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์€ ์‹ค์ œ๋กœ ๋‹จ ํ•˜๋‚˜์˜ ์„ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋ทฐ๊ฐ€ ์ƒํƒœ์— ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. iOS์—์„œ ReSwift๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ์‹ฌ์ง€์–ด ์ž‡๋‹ค React๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด SwiftUI์˜ ๋งˆ๋ฒ•์„ ์ดํ•ดํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์ œ ์ž‘์—…์„ ํ™œ์„ฑํ™”ํ•˜๊ณ  ์ƒˆ ์ƒํƒœ๋ฅผ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๋” ๋ณต์žกํ•œ ์˜ˆ์ž…๋‹ˆ๋‹ค.

struct CustomListDetail : View {
    @EnvironmentObject var store: Store<AppState>
 
    let listId: Int
    
    var list: CustomList {
        store.state.moviesState.customLists[listId]!
    }
    
    var movies: [Int] {
        list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state)
    }
    
    var body: some View {
        List {
            ForEach(movies) { movie in
                NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) {
                    MovieRow(movieId: movie, displayListImage: false)
                }
            }.onDelete { (index) in
               self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!]))
            }
        }
    }
}

์œ„ ์ฝ”๋“œ์—์„œ๋Š” ๊ฐ IP์— ๋Œ€ํ•ด SwiftUI์˜ .onDelete ์ž‘์—…์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ชฉ๋ก์˜ ํ–‰์— ์ผ๋ฐ˜ iOS ์Šค์™€์ดํ”„๋ฅผ ํ‘œ์‹œํ•˜์—ฌ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ญ์ œ ๋ฒ„ํŠผ์„ ํ„ฐ์น˜ํ•˜๋ฉด ํ•ด๋‹น ์ž‘์—…์ด ์‹คํ–‰๋˜๊ณ  ๋ชฉ๋ก์—์„œ ์˜ํ™”๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

์Œ, ๋ชฉ๋ก ์†์„ฑ์€ BindableObject ์ƒํƒœ์—์„œ ํŒŒ์ƒ๋˜๊ณ  EnvironmentObject๋กœ ์ฃผ์ž…๋˜๋ฏ€๋กœ ForEach๊ฐ€ ์˜ํ™” ๊ณ„์‚ฐ ์†์„ฑ๊ณผ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— SwiftUI๋Š” ๋ชฉ๋ก์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ MoviesState ๊ฐ์†๊ธฐ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // other actions.
    
    case let action as MoviesActions.AddMovieToCustomList:
        state.customLists[action.list]?.movies.append(action.movie)
        
    case let action as MoviesActions.RemoveMovieFromCustomList:
        state.customLists[action.list]?.movies.removeAll{ $0 == action.movie }
        
    default:
        break
    }
    return state
}

์œ„์—์„œ ์„ค๋ช…ํ•œ ๋Œ€๋กœ ์•ก์…˜์„ ์ „๋‹ฌํ•˜๊ณ  ์ƒˆ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฆฌ๋“€์„œ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์•„์ง์€ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค - ์–ด๋–ป๊ฒŒ SwiftUI๊ฐ€ ํ‘œ์‹œํ•  ๋‚ด์šฉ์„ ์‹ค์ œ๋กœ ์•„๋Š”์ง€. ์ด๊ฒƒ์„ ๋” ๊นŠ์ด ์ดํ•ดํ•˜๋ ค๋ฉด ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ํ๋ฆ„์— ๋Œ€ํ•œ WWDC ์„ธ์…˜ ๋ณด๊ธฐ SwiftUI์—์„œ. ์™œ, ์–ธ์ œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€ ์ž์„ธํžˆ ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ ์ •๋ถ€, @Binding, ObjectBinding ๋ฐ EnvironmentObject.

Skillbox๋Š” ๋‹ค์Œ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€