Mengadaptasi solusi bisnis Anda yang sudah ada untuk SwiftUI. Bagian 3. Bekerja dengan arsitektur

Hari baik untuk semua! Bersama Anda, saya, Anna Zharkova, pengembang seluler terkemuka Usetech, kami

terus membongkar seluk-beluk SwiftUI. Bagian sebelumnya dapat ditemukan di tautan:

bagian 1
bagian 2

Hari ini kita akan berbicara tentang fitur arsitektur, dan cara mentransfer dan menanamkan logika bisnis yang ada ke dalam aplikasi SwiftUI.

Aliran data standar dalam SwiftUI didasarkan pada interaksi View dan model tertentu yang berisi properti dan variabel keadaan, atau yang merupakan variabel keadaan tersebut. Oleh karena itu, adalah logis bahwa MVVM adalah mitra arsitektur yang direkomendasikan untuk aplikasi SwiftUI. Apple menyarankan untuk menggunakannya bersamaan dengan kerangka kerja Combine, yang memperkenalkan Api SwiftUI deklaratif untuk memproses nilai dari waktu ke waktu. ViewModel mengimplementasikan protokol ObservableObject dan menghubungkannya sebagai ObservedObject ke View tertentu.



Properti model yang dapat dimodifikasi dinyatakan sebagai @Published.

class NewsItemModel: ObservableObject {
    @Published var title: String = ""
    @Published var description: String = ""
    @Published var image: String = ""
    @Published var dateFormatted: String = ""
}

Seperti dalam MVVM klasik, ViewModel berkomunikasi dengan model data (mis. Logika bisnis) dan mentransfer data dalam satu bentuk atau lainnya.

struct NewsItemContentView: View {
    @ObservedObject var moder: NewsItemModel
    
    init(model: NewsItemModel) {
        self.model = model 
    }
    //... - 
}

MVVM, seperti hampir semua pola lainnya, memiliki kecenderungan kemacetan dan
redundansi. ViewModel yang kelebihan beban selalu bergantung pada seberapa baik logika bisnis disorot dan disarikan. Beban Tampilan ditentukan oleh kompleksitas ketergantungan elemen pada variabel status dan transisi ke Tampilan lainnya.

Di SwiftUI, yang ditambahkan adalah bahwa View adalah struktur, bukan kelas , dan karenanya tidak mendukung warisan, memaksa duplikasi kode.
Jika dalam aplikasi kecil ini tidak kritis, maka dengan pertumbuhan fungsionalitas dan kompleksitas logika, kelebihan menjadi kritis, dan sejumlah besar menghambat copy-paste.

Mari kita coba menggunakan pendekatan kode bersih dan arsitektur bersih dalam kasus ini. Kami tidak bisa sepenuhnya meninggalkan MVVM, lagipula, DataFlow SwiftUI dibangun di atasnya, tetapi cukup sedikit untuk membangun kembali.

Peringatan!

Jika Anda alergi terhadap artikel tentang arsitektur, dan Bersihkan kode dari frase, gulir ke bawah beberapa paragraf.
Ini bukan kode Bersihkan dari Paman Bob!


Ya, kami tidak akan mengambil Kode Bersih Paman Bob dalam bentuk yang paling murni. Bagi saya, ada over-engineering di dalamnya. Kami hanya akan mengambil ide.

Gagasan utama kode bersih adalah membuat kode yang paling mudah dibaca, yang kemudian dapat diperluas dan dimodifikasi tanpa rasa sakit.

Ada beberapa prinsip pengembangan perangkat lunak yang direkomendasikan untuk dipatuhi.



Banyak orang mengenal mereka, tetapi tidak semua orang menyukai dan tidak semua orang menggunakannya. Ini adalah topik terpisah untuk holivar.

Untuk memastikan kemurnian kode, setidaknya perlu untuk membagi kode menjadi lapisan fungsional dan modul, gunakan solusi umum masalah dan menerapkan abstraksi interaksi antara komponen. Dan setidaknya Anda perlu memisahkan kode UI dari apa yang disebut logika bisnis.

Terlepas dari pola arsitektur yang dipilih, logika bekerja dengan database dan jaringan, pemrosesan dan penyimpanan data dipisahkan dari UI dan modul aplikasi itu sendiri. Pada saat yang sama, modul bekerja dengan implementasi layanan atau penyimpanan, yang pada gilirannya mengakses layanan umum dari permintaan jaringan atau penyimpanan data umum. Inisialisasi variabel dengan mana Anda dapat mengakses satu atau layanan lain dilakukan dalam wadah umum tertentu, di mana modul aplikasi (modul logika bisnis) akhirnya mengakses.



Jika kita telah memilih dan mengabstraksi logika bisnis, maka kita dapat mengatur interaksi antara komponen-komponen modul sesuka kita.

Pada prinsipnya, semua pola aplikasi iOS yang ada beroperasi dengan prinsip yang sama.



Selalu ada logika bisnis, ada data. Ada juga manajer panggilan, yang bertanggung jawab untuk penyajian dan transformasi data untuk output dan di mana data yang dikonversi adalah output. Satu-satunya perbedaan adalah bagaimana peran didistribusikan antar komponen.


Karena kami berusaha keras agar aplikasi dapat dibaca, untuk menyederhanakan perubahan saat ini dan di masa depan, adalah logis untuk memisahkan semua peran ini. Logika bisnis kami telah disorot, data selalu dipisahkan. Tetap pengirim, presenter dan lihat. Sebagai hasilnya, kami mendapatkan arsitektur yang terdiri dari View-Interactor-Presenter, di mana interaksor berinteraksi dengan layanan logika bisnis, presenter mengubah data dan memberikannya sebagai semacam ViewModel ke View kami. Dengan cara yang baik, navigasi dan konfigurasi juga diambil dari View menjadi komponen yang terpisah.



Kami mendapatkan arsitektur VIP + R dengan pembagian peran kontroversial ke dalam berbagai komponen.

Mari kita coba lihat sebuah contoh. Kami memiliki aplikasi agregator berita kecil yang
ditulis dalam bahasa SwiftUI dan MVVM.



Aplikasi memiliki 3 layar terpisah dengan logikanya sendiri, yaitu 3 modul:โ€จ

  • modul daftar berita;
  • modul layar berita;
  • modul pencarian berita.

Setiap modul terdiri dari ViewModel, yang berinteraksi dengan logika bisnis yang dipilih, dan View, yang menampilkan apa yang disiarkan ViewModel padanya.



Kami berusaha keras untuk memastikan bahwa ViewModel hanya peduli dengan menyimpan data yang siap untuk ditampilkan. Sekarang dia terlibat dalam mengakses layanan dan memproses hasilnya.

Kami mentransfer peran-peran ini ke presenter dan berinteraksi, yang kami siapkan untuk setiap
modul.



Interaktor mengirimkan data yang diterima dari layanan ke presenter, yang mengisi ViewModel yang ada terikat dengan View dengan data yang disiapkan. Pada prinsipnya, berkenaan dengan pemisahan logika bisnis dari modul, semuanya sederhana. โ€จ

Sekarang buka View. Mari kita coba menangani duplikasi kode paksa. Jika kita berhadapan dengan semacam kontrol, maka itu mungkin gaya atau pengaturannya. Jika kita berbicara tentang Tampilan di layar, maka ini:

  • Gaya layar
  • elemen UI umum (LoadingView);
  • peringatan informasi;
  • beberapa metode umum.

Kita tidak bisa menggunakan warisan, tetapi kita bisa menggunakan komposisi . Dengan prinsip inilah semua Tampilan kustom di SwiftUI dibuat.

Jadi, kami membuat wadah Tampilan, di mana kami mentransfer semua logika yang sama, dan meneruskan Tampilan di layar ke penginisialisasi wadah dan kemudian menggunakannya sebagai Tampilan konten di dalam tubuh.

struct ContainerView<Content>: IContainer, View where Content: View {
    @ObservedObject var containerModel = ContainerModel()
    private var content: Content

    public init(content: Content) {
        self.content = content
    }

    var body : some View {
        ZStack {
            content
            if (self.containerModel.isLoading) {
                LoaderView()
            }
        }.alert(isPresented: $containerModel.hasError){
            Alert(title: Text(""), message: Text(containerModel.errorText),
                 dismissButton: .default(Text("OK")){
                self.containerModel.errorShown()
                })
        }
    }

Tampilan di layar tertanam dalam ZStack di dalam body ContainerView, yang juga berisi kode untuk menampilkan LoadingView dan kode untuk menampilkan peringatan informasi.

Kita juga membutuhkan ContainerView untuk menerima sinyal dari Model View dari tampilan internal dan memperbarui kondisinya. Kami tidak dapat berlangganan melalui @Observed ke model yang sama
dengan tampilan internal, karena kami akan menyeret sinyalnya.



Oleh karena itu, kami menjalin komunikasi dengannya melalui pola delegasi, dan untuk kondisi wadah saat ini kami menggunakan ContainerModel sendiri.

class ContainerModel:ObservableObject {
    @Published var hasError: Bool = false
    @Published var errorText: String = ""
    @Published var isLoading: Bool = false
    
    func setupError(error: String){
     //....
       }
    
    func errorShown() {
     //...
    }
    
    func showLoading() {
        self.isLoading = true
    }
    
    func hideLoading() {
        self.isLoading = false
    }
}

ContainerView mengimplementasikan protokol IContainer, sebuah referensi instance ditugaskan untuk model View tertanam.

protocol  IContainer {
    func showError(error: String)
    
    func showLoading()
    
    func hideLoading()
}

struct ContainerView<Content>: IContainer, View where Content: View&IModelView {
    @ObservedObject var containerModel = ContainerModel()
    private var content: Content

    public init(content: Content) {
        self.content = content
        self.content.viewModel?.listener = self
    }
    //- 
}

Lihat mengimplementasikan protokol IModelView untuk merangkum akses model dan menyatukan beberapa logika. Model untuk tujuan yang sama mengimplementasikan protokol IModel:

protocol IModelView {
    var viewModel: IModel? {get}
}

protocol  IModel:class {
   //....
    var listener:IContainer? {get set}
}

Kemudian, sudah dalam model ini, jika perlu, metode delegasi dipanggil, misalnya, untuk menampilkan peringatan dengan kesalahan di mana variabel keadaan model wadah berubah.

struct ContainerView<Content>: IContainer, View where Content: View&IModelView {
    @ObservedObject var containerModel = ContainerModel()
    private var content: Content

    //- 
    func showError(error: String) {
        self.containerModel.setupError(error: error)
    }
    
    func showLoading() {
        self.containerModel.showLoading()
    }
    
    func hideLoading() {
        self.containerModel.hideLoading()
    }
}

Sekarang kita dapat menyatukan pekerjaan View dengan beralih ke bekerja melalui ContainerView.
Ini akan sangat memudahkan hidup kita ketika bekerja dengan konfigurasi modul dan navigasi berikut.
Bagaimana mengkonfigurasi navigasi di SwiftUI dan membuat konfigurasi bersih, kita akan berbicara di depan bagian .

Anda dapat menemukan kode sumber dari contoh di sini .

All Articles