Sovelluskehitys SwiftUI:lla. Osa 1: Dataflow ja Redux

Sovelluskehitys SwiftUI:lla. Osa 1: Dataflow ja Redux

Osallistuttuani State of the Union -istuntoon WWDC 2019 -tapahtumassa päätin sukeltaa syvälle SwiftUI:han. Olen viettänyt paljon aikaa sen parissa ja nyt olen alkanut kehittää todellista sovellusta, josta voi olla hyötyä useille käyttäjille.

Kutsuin sitä MovieSwiftUI:ksi - tämä on sovellus uusien ja vanhojen elokuvien etsimiseen sekä niiden keräämiseen kokoelmaan TMDB API. Olen aina rakastanut elokuvia ja jopa perustanut tällä alalla työskentelevän yrityksen, vaikka kauan sitten. Yritystä tuskin voisi kutsua cooliksi, mutta hakemus oli!

Muistutamme sinua: kaikille "Habrin" lukijoille - 10 000 ruplan alennus ilmoittautuessaan mille tahansa Skillbox-kurssille "Habr" -tarjouskoodilla.

Skillbox suosittelee: Kouluttava verkkokurssi "Ammatti Java-kehittäjä".

Mitä MovieSwiftUI voi tehdä?

  • Toimii vuorovaikutuksessa API:n kanssa - melkein kaikki nykyaikaiset sovellukset tekevät tämän.
  • Lataa asynkronista dataa pyyntöihin ja jäsentää JSONin Swift-malliin käyttämällä Koodattava.
  • Näyttää pyynnöstä ladatut kuvat ja tallentaa ne välimuistiin.
  • Tämä iOS-, iPadOS- ja macOS-sovellus tarjoaa parhaan käyttökokemuksen näiden käyttöjärjestelmien käyttäjille.
  • Käyttäjä voi luoda tietoja ja luoda omia elokuvalistoja. Sovellus tallentaa ja palauttaa käyttäjätiedot.
  • Näkymät, komponentit ja mallit erotetaan selvästi Redux-kuviolla. Tietovirta täällä on yksisuuntaista. Se voidaan tallentaa kokonaan välimuistiin, palauttaa ja korvata.
  • Sovellus käyttää peruskomponentteja SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal jne. Se tarjoaa myös mukautettuja näkymiä, eleitä, käyttöliittymän/UX:n.

Sovelluskehitys SwiftUI:lla. Osa 1: Dataflow ja Redux
Itse asiassa animaatio on sileä, GIF osoittautui hieman nykiväksi

Sovelluksen parissa työskentely antoi minulle paljon kokemusta ja kaiken kaikkiaan se oli positiivinen kokemus. Pystyin kirjoittamaan täysin toimivan sovelluksen, syyskuussa parantelen sitä ja julkaisen sen AppStoressa, samanaikaisesti iOS 13:n julkaisun kanssa.

Redux, BindableObject ja EnvironmentObject

Sovelluskehitys SwiftUI:lla. Osa 1: Dataflow ja Redux

Olen työskennellyt Reduxin kanssa nyt noin kaksi vuotta, joten olen suhteellisen hyvin perehtynyt siihen. Käytän sitä erityisesti käyttöliittymässä suhtautua verkkosivuilla sekä natiivi iOS (Swift) ja Android (Kotlin) sovellusten kehittämiseen.

En ole koskaan katunut, että valitsin Reduxin tietovirta-arkkitehtuuriksi SwiftUI-sovelluksen rakentamiseen. Haastavimmat osat käytettäessä Reduxia UIKit-sovelluksessa ovat kaupan kanssa työskentely sekä tietojen hakeminen ja hakeminen sekä niiden yhdistäminen näkymiin/komponentteihin. Tätä varten minun piti luoda eräänlainen liitinkirjasto (ReSwiftin ja ReKotlinin avulla). Toimii hyvin, mutta paljon koodia. Valitettavasti se ei ole (vielä) avoin lähdekoodi.

Hyviä uutisia! Ainoat huolenaiheet SwiftUI:n kanssa - jos aiot käyttää Reduxia - ovat kaupat, osavaltiot ja supistimet. SwiftUI huolehtii täysin vuorovaikutuksesta kaupan kanssa @EnvironmentObjectin ansiosta. Joten kauppa alkaa BindableObjectilla.

Tein yksinkertaisen Swift-paketin, SwiftUIFlux, joka tarjoaa Reduxin peruskäytön. Minun tapauksessani se on osa MovieSwiftUI:ta. minä myös kirjoitti vaiheittaisen opetusohjelman, joka auttaa sinua käyttämään tätä komponenttia.

Miten se toimii?

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

Joka kerta kun käynnistät toiminnon, aktivoit vaihteiston. Se arvioi toimia sovelluksen nykyisen tilan mukaan. Sitten se palauttaa uuden muokatun tilan toimintotyypin ja tietojen mukaan.

No, koska kauppa on BindableObject, se ilmoittaa SwiftUI:lle, kun sen arvo muuttuu käyttämällä PassthroughSubjectin tarjoamaa willChange-ominaisuutta. Tämä johtuu siitä, että BindableObjectin on tarjottava PublisherType, mutta protokollan toteutus on vastuussa sen hallinnasta. Kaiken kaikkiaan tämä on erittäin tehokas työkalu Applelta. Vastaavasti seuraavassa renderöintijaksossa SwiftUI auttaa renderöimään näkymien rungon tilanmuutoksen mukaan.

Itse asiassa tämä on kaikki SwiftUI:n sydän ja taika. Nyt kaikissa näkymissä, jotka tilaavat tilan, näkymä esitetään sen mukaan, mitä tietoja tilasta on vastaanotettu ja mikä on muuttunut.

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

Kauppa syötetään ympäristöobjektina, kun sovellus käynnistyy, ja se on sitten käytettävissä missä tahansa näkymässä @EnvironmentObjectin avulla. Suorituskykyä ei peritä, koska johdetut ominaisuudet noudetaan tai lasketaan nopeasti sovelluksen tilasta.

Yllä oleva koodi muuttaa kuvaa, jos elokuvan juliste muuttuu.

Ja tämä tehdään itse asiassa vain yhdellä rivillä, jonka avulla näkymät yhdistetään valtioon. Jos olet työskennellyt ReSwiftin kanssa iOS:ssä tai jopa kytkeä Reactin avulla ymmärrät SwiftUI:n taian.

Nyt voit yrittää aktivoida toiminnon ja julkaista uuden tilan. Tässä on monimutkaisempi esimerkki.

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

Yllä olevassa koodissa käytän SwiftUI:n .onDelete-toimintoa jokaiselle IP-osoitteelle. Tämä sallii luettelon rivin näyttää tavallisen iOS-pyyhkäisyn poistamiseksi. Joten kun käyttäjä koskettaa poistopainiketta, se käynnistää vastaavan toiminnon ja poistaa elokuvan luettelosta.

Koska luetteloominaisuus on johdettu BindableObject-tilasta ja se syötetään ympäristöobjektina, SwiftUI päivittää luettelon, koska ForEach liittyy elokuvien laskettuun ominaisuuteen.

Tässä on osa MoviesState-vähennystä:

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
}

Vähentäjä suoritetaan, kun lähetät toiminnon ja palautat uuden tilan, kuten yllä on kerrottu.

En mene vielä yksityiskohtiin - kuinka SwiftUI todella tietää, mitä näyttää. Ymmärtääksesi tämän syvemmin, se kannattaa tarkastella WWDC-istuntoa tiedonkulusta SwiftUI:ssa. Se selittää myös yksityiskohtaisesti, miksi ja milloin käyttää Osavaltio, @Binding, ObjectBinding ja EnvironmentObject.

Skillbox suosittelee:

Lähde: will.com

Lisää kommentti