Pengembangan aplikasi di SwiftUI. Bagian 1: Aliran Data dan Redux

Pengembangan aplikasi di SwiftUI. Bagian 1: Aliran Data dan Redux

Setelah menghadiri sesi State of the Union di WWDC 2019, saya memutuskan untuk mendalami SwiftUI. Saya telah menghabiskan banyak waktu mengerjakannya dan kini mulai mengembangkan aplikasi nyata yang dapat berguna bagi banyak pengguna.

Saya menyebutnya MovieSwiftUI - ini adalah aplikasi untuk mencari film baru dan lama, serta mengumpulkannya dalam koleksi menggunakan API TMDB. Saya selalu menyukai film dan bahkan mendirikan perusahaan yang bergerak di bidang ini, meskipun sudah lama sekali. Perusahaan itu hampir tidak bisa disebut keren, tetapi aplikasinya keren!

Kami mengingatkan: untuk semua pembaca "Habr" - diskon 10 rubel saat mendaftar di kursus Skillbox apa pun menggunakan kode promosi "Habr".

Skillbox merekomendasikan: Kursus online pendidikan "Profesi Pengembang Java".

Jadi apa yang bisa dilakukan MovieSwiftUI?

  • Berinteraksi dengan API - hampir semua aplikasi modern melakukan hal ini.
  • Memuat data asinkron berdasarkan permintaan dan mem-parsing JSON ke dalam model Swift menggunakan Dapat dikodekan.
  • Menampilkan gambar yang dimuat berdasarkan permintaan dan menyimpannya dalam cache.
  • Aplikasi untuk iOS, iPadOS, dan macOS ini memberikan UX terbaik bagi pengguna OS tersebut.
  • Pengguna dapat menghasilkan data dan membuat daftar filmnya sendiri. Aplikasi menyimpan dan memulihkan data pengguna.
  • Tampilan, komponen, dan model dipisahkan dengan jelas menggunakan pola Redux. Aliran data di sini bersifat searah. Itu dapat di-cache sepenuhnya, dipulihkan, dan ditimpa.
  • Aplikasi ini menggunakan komponen dasar SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, dll. Ini juga menyediakan tampilan khusus, gerakan, UI/UX.

Pengembangan aplikasi di SwiftUI. Bagian 1: Aliran Data dan Redux
Nyatanya animasinya mulus, GIFnya agak tersentak-sentak

Mengerjakan aplikasi ini memberi saya banyak pengalaman dan secara keseluruhan itu adalah pengalaman yang positif. Saya dapat menulis aplikasi yang berfungsi penuh, pada bulan September saya akan memperbaikinya dan mempublikasikannya di AppStore, bersamaan dengan rilis iOS 13.

Redux, BindableObject dan EnvironmentObject

Pengembangan aplikasi di SwiftUI. Bagian 1: Aliran Data dan Redux

Saya telah bekerja dengan Redux selama sekitar dua tahun, jadi saya cukup berpengalaman di dalamnya. Secara khusus, saya menggunakannya di frontend untuk Bereaksi situs web, serta untuk mengembangkan aplikasi asli iOS (Swift) dan Android (Kotlin).

Saya tidak pernah menyesal memilih Redux sebagai arsitektur aliran data untuk membangun aplikasi SwiftUI. Bagian yang paling menantang saat menggunakan Redux di aplikasi UIKit adalah bekerja dengan penyimpanan dan mendapatkan serta mengambil data dan memetakannya ke tampilan/komponen Anda. Untuk melakukan ini, saya harus membuat semacam perpustakaan konektor (menggunakan ReSwift dan ReKotlin). Berfungsi dengan baik, tetapi kodenya cukup banyak. Sayangnya, ini (belum) open source.

Kabar baik! Satu-satunya hal yang perlu dikhawatirkan dengan SwiftUI - jika Anda berencana menggunakan Redux - adalah penyimpanan, status, dan reduksi. Interaksi dengan toko sepenuhnya ditangani oleh SwiftUI berkat @EnvironmentObject. Jadi, penyimpanan dimulai dengan BindableObject.

Saya membuat paket Swift sederhana, SwiftUIFlux, yang menyediakan penggunaan dasar Redux. Dalam kasus saya ini adalah bagian dari MovieSwiftUI. saya juga menulis tutorial langkah demi langkah, yang akan membantu Anda menggunakan komponen ini.

Bagaimana cara kerjanya?

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

Setiap kali Anda memicu suatu tindakan, Anda mengaktifkan gearbox. Ini akan mengevaluasi tindakan berdasarkan keadaan aplikasi saat ini. Ini kemudian akan mengembalikan keadaan baru yang dimodifikasi sesuai dengan jenis tindakan dan data.

Karena store adalah BindableObject, ia akan memberi tahu SwiftUI ketika nilainya berubah menggunakan properti willChange yang disediakan oleh PassthroughSubject. Hal ini karena BindableObject harus menyediakan PublisherType, namun implementasi protokol bertanggung jawab untuk mengelolanya. Secara keseluruhan, ini adalah alat yang sangat ampuh dari Apple. Oleh karena itu, pada siklus rendering berikutnya, SwiftUI akan membantu merender isi tampilan sesuai dengan perubahan status.

Sebenarnya, ini adalah inti dan keajaiban SwiftUI. Sekarang, dalam tampilan apa pun yang berlangganan suatu negara, tampilan tersebut akan dirender sesuai dengan data apa yang diterima dari negara tersebut dan apa yang telah berubah.

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 dimasukkan sebagai EnvironmentObject saat aplikasi dimulai dan kemudian dapat diakses dalam tampilan apa pun menggunakan @EnvironmentObject. Tidak ada penalti kinerja karena properti turunan diambil atau dihitung dengan cepat dari status aplikasi.

Kode di atas mengubah gambar jika poster film berubah.

Dan ini sebenarnya dilakukan hanya dengan satu baris, yang dengannya pandangan dihubungkan dengan negara. Jika Anda pernah bekerja dengan ReSwift di iOS atau bahkan menghubungkan dengan React, Anda akan memahami keajaiban SwiftUI.

Sekarang Anda dapat mencoba mengaktifkan tindakan dan mempublikasikan status baru. Berikut ini contoh yang lebih kompleks.

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

Pada kode di atas, saya menggunakan tindakan .onDelete dari SwiftUI untuk setiap IP. Ini memungkinkan baris dalam daftar menampilkan gesekan iOS normal untuk dihapus. Jadi, ketika pengguna menyentuh tombol hapus, tindakan tersebut akan terpicu dan menghapus film tersebut dari daftar.

Nah, karena properti daftar berasal dari status BindableObject dan dimasukkan sebagai EnvironmentObject, SwiftUI memperbarui daftar karena ForEach dikaitkan dengan properti terhitung film.

Ini adalah bagian dari peredam 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
}

Peredam dijalankan ketika Anda mengirimkan tindakan dan mengembalikan keadaan baru, seperti yang dinyatakan di atas.

Saya belum akan menjelaskan secara detail - bagaimana SwiftUI sebenarnya mengetahui apa yang harus ditampilkan. Untuk memahami hal ini lebih dalam, ada baiknya lihat sesi WWDC tentang aliran data di SwiftUI. Ini juga menjelaskan secara rinci mengapa dan kapan harus digunakan Negara, @Binding, ObjectBinding dan EnvironmentObject.

Skillbox merekomendasikan:

Sumber: www.habr.com

Tambah komentar