Applikationsudvikling på SwiftUI. Del 1: Dataflow og Redux

Applikationsudvikling på SwiftUI. Del 1: Dataflow og Redux

Efter at have deltaget i State of the Union-sessionen på WWDC 2019 besluttede jeg at tage et dybt dyk ned i SwiftUI. Jeg har brugt meget tid på at arbejde med det og er nu begyndt at udvikle en rigtig applikation, som kan være nyttig for en bred vifte af brugere.

Jeg kaldte det MovieSwiftUI - dette er en app til at søge efter nye og gamle film, samt samle dem i en samling vha. TMDB API. Jeg har altid elsket film og har endda oprettet et firma, der arbejder på dette område, selvom det er længe siden. Firmaet kunne næsten ikke kaldes cool, men ansøgningen var!

Påmindelse: for alle læsere af "Habr" - en rabat på 10 rubler ved tilmelding til ethvert Skillbox-kursus ved hjælp af "Habr"-kampagnekoden.

Skillbox anbefaler: Pædagogisk online kursus "Profession Java Developer".

Så hvad kan MovieSwiftUI gøre?

  • Interagerer med API'et - næsten enhver moderne applikation gør dette.
  • Indlæser asynkrone data på anmodninger og parser JSON ind i Swift-modellen vha Kodbar.
  • Viser billeder indlæst efter anmodning og cacher dem.
  • Denne app til iOS, iPadOS og macOS giver den bedste UX for brugere af disse operativsystemer.
  • Brugeren kan generere data og lave deres egne filmlister. Applikationen gemmer og gendanner brugerdata.
  • Visninger, komponenter og modeller er tydeligt adskilt ved hjælp af Redux-mønsteret. Datastrømmen her er ensrettet. Det kan være fuldt cachelagret, gendannet og overskrevet.
  • Applikationen bruger de grundlæggende komponenter i SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal osv. Det giver også brugerdefinerede visninger, bevægelser, UI/UX.

Applikationsudvikling på SwiftUI. Del 1: Dataflow og Redux
Faktisk er animationen glat, GIF'en viste sig lidt ryk

Arbejdet med appen gav mig en masse erfaring og generelt var det en positiv oplevelse. Jeg var i stand til at skrive en fuldt funktionel applikation, i september vil jeg forbedre den og udgive den i AppStore, samtidig med udgivelsen af ​​iOS 13.

Redux, BindableObject og EnvironmentObject

Applikationsudvikling på SwiftUI. Del 1: Dataflow og Redux

Jeg har arbejdet med Redux i omkring to år nu, så jeg er relativt velbevandret i det. Især bruger jeg den i frontend til Reagerer hjemmeside, samt til udvikling af native iOS (Swift) og Android (Kotlin) applikationer.

Jeg har aldrig fortrudt, at jeg valgte Redux som dataflow-arkitekturen til at bygge en SwiftUI-applikation. De mest udfordrende dele, når du bruger Redux i en UIKit-app, er at arbejde med butikken og hente og hente data og kortlægge dem til dine visninger/komponenter. For at gøre dette var jeg nødt til at oprette et slags bibliotek af connectors (ved hjælp af ReSwift og ReKotlin). Fungerer godt, men ret meget kode. Desværre er det (endnu) ikke open source.

Gode ​​nyheder! De eneste ting, du skal bekymre dig om med SwiftUI - hvis du planlægger at bruge Redux - er butikker, stater og reducering. Interaktion med butikken varetages fuldstændig af SwiftUI takket være @EnvironmentObject. Så butikken starter med et BindableObject.

Jeg lavede en simpel Swift-pakke, SwiftUIFlux, som giver grundlæggende brug af Redux. I mit tilfælde er det en del af MovieSwiftUI. Også mig skrev en trin-for-trin tutorial, som vil hjælpe dig med at bruge denne komponent.

Hvordan fungerer det?

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)
    }
}

Hver gang du udløser en handling, aktiverer du gearkassen. Det vil evaluere handlinger i henhold til den aktuelle tilstand af ansøgningen. Det vil derefter returnere en ny ændret tilstand i overensstemmelse med handlingstypen og dataene.

Nå, da butikken er et BindableObject, vil den underrette SwiftUI, når dens værdi ændres ved hjælp af willChange-egenskaben leveret af PassthroughSubject. Dette skyldes, at BindableObject skal levere en PublisherType, men protokolimplementeringen er ansvarlig for at administrere den. Alt i alt er dette et meget kraftfuldt værktøj fra Apple. Derfor vil SwiftUI i den næste gengivelsescyklus hjælpe med at gengive hoveddelen af ​​visningerne i henhold til tilstandsændringen.

Faktisk er dette hele hjertet og magien ved SwiftUI. Nu, i enhver visning, der abonnerer på en stat, vil visningen blive gengivet i overensstemmelse med, hvilke data der modtages fra staten, og hvad der er ændret.

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())
    }
}

Butikken injiceres som et EnvironmentObject, når applikationen starter, og er derefter tilgængelig i enhver visning ved hjælp af @EnvironmentObject. Der er ingen præstationsstraf, fordi afledte egenskaber hurtigt hentes eller beregnes fra applikationstilstand.

Koden ovenfor ændrer billedet, hvis filmplakaten ændres.

Og det sker faktisk med kun én linje, ved hjælp af hvilken synspunkter kobles til staten. Hvis du har arbejdet med ReSwift på iOS eller endda connect med React vil du forstå magien ved SwiftUI.

Nu kan du prøve at aktivere handlingen og udgive den nye tilstand. Her er et mere komplekst eksempel.

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!]))
            }
        }
    }
}

I koden ovenfor bruger jeg handlingen .onDelete fra SwiftUI for hver IP. Dette gør det muligt for rækken på listen at vise det normale iOS-stryg for at slette. Så når brugeren trykker på sletknappen, udløser den den tilsvarende handling og fjerner filmen fra listen.

Nå, da listeegenskaben er afledt af BindableObject-tilstanden og injiceres som et EnvironmentObject, opdaterer SwiftUI listen, fordi ForEach er knyttet til den filmberegnede egenskab.

Her er en del af MoviesState-reduceren:

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
}

Reduktionen udføres, når du sender en handling og returnerer en ny tilstand, som angivet ovenfor.

Jeg vil ikke gå i detaljer endnu - hvordan SwiftUI faktisk ved, hvad der skal vises. For at forstå dette dybere, er det værd se WWDC-session om dataflow i SwiftUI. Den forklarer også i detaljer, hvorfor og hvornår den skal bruges Tilstand, @Binding, ObjectBinding og EnvironmentObject.

Skillbox anbefaler:

Kilde: www.habr.com

Tilføj en kommentar