API untuk pengambilan asinkron jarak jauh menggunakan Apple Combine



Combine adalah kerangka kerja Swift reaktif fungsional yang baru-baru ini diterapkan untuk semua platform Apple , termasuk Xcode 11 . Dengan Combine, sangat mudah untuk memproses urutan nilai yang muncul secara sinkron seiring waktu. Ini juga menyederhanakan kode asinkron dengan meninggalkan delegasi dan panggilan balik bersarang yang kompleks.

Tetapi mempelajari Combine sendiri pada awalnya mungkin tidak tampak begitu sederhana. Faktanya adalah bahwa "pemain" utama Combine adalah konsep abstrak seperti "penerbit" Penerbit , "pelanggan" Pelanggan dan operator Operator , yang tanpanya tidak akan mungkin untuk maju dalam memahami logika fungsi Combine . Namun, karena fakta bahwa Apple memberi pengembang "penerbit" yang siap pakai, "pelanggan" dan operator, kode yang ditulis dengan Combine sangat ringkas dan mudah dibaca.

Anda akan melihat ini dalam contoh aplikasi yang berkaitan dengan mengambil informasi film secara tidak sinkron dari basis data TMDb yang sangat populer sekarang . Kami akan membuat dua aplikasi berbeda: UIKit dan SwiftUI , dan menunjukkan bagaimana Combine bekerja dengannya.



Saya harap artikel ini memudahkan Anda mempelajari Combine . Kode untuk semua aplikasi yang dikembangkan dalam artikel ini dapat ditemukan di Github .

Combine memiliki beberapa komponen utama:

Penerbit Penerbit .




Penerbit Penerbit adalah JENIS yang memberikan nilai kepada semua orang yang peduli. Konsep "penerbit" penerbit diimplementasikan dalam Combine sebagai protokol , bukan TYPE tertentu. Protokol Penerbit telah mengaitkan JENIS Generik untuk nilai output dan kesalahan Kegagalan .
"Penerbit" yang tidak pernah menerbitkan kesalahan menggunakan TYPE Never for a Failure error.





Apple menyediakan pengembang dengan implementasi spesifik dari "penerbit" yang siap pakai: Just , Future , Empty , Deferred , Sequence , @Published , dll. "Penerbit" juga ditambahkan ke kelas Foundation : URLSession , < NotificationCenter , Timer .

Pelanggan "Pelanggan".




Ini juga merupakan protokol protokol yang menyediakan antarmuka untuk "berlangganan" ke nilai - nilai dari "penerbit". Ini telah mengaitkan JENIS Generik untuk kesalahan Input dan Kegagalan . Jelas, JENIS Penerbit Penerbit dan Pelanggan harus cocok.



Untuk setiap penerbit Publisher, ada dua Pelanggan built-in: sink dan assign :



Wastafel "Pelanggan" didasarkan pada dua penutupan: satu penutupan, acceptValue , dieksekusi ketika Anda mendapatkan nilai - nilai , penutupan kedua, acceptCompletion , dieksekusi ketika "publikasi" selesai (biasanya atau dengan kesalahan).



"Pelanggan" menetapkan , memajukan setiap nilai yang diterima, menuju key Path ditentukan.

Berlangganan "Berlangganan".




Pertama, Penerbit "penerbit" membuat dan mengirimkan Berlangganan " Berlangganan " ke Pelanggan " Pelanggan " melalui metode terima (berlangganan :) :



Setelah itu, Langganan dapat mengirim nilainya ke "pelanggan" Pelanggan menggunakan dua metode:



Jika Anda telah selesai bekerja dengan Berlangganan Berlangganan , Anda dapat memanggil metode batal () :



"Subjek" Subjek .




Ini adalah protokol protokol yang menyediakan antarmuka untuk kedua klien, baik untuk "penerbit" dan untuk "pelanggan". Pada dasarnya, subjek "subjek" adalah Penerbit "penerbit", yang dapat menerima input nilai input dan yang dapat Anda gunakan untuk "menyuntikkan" nilai - nilai ke dalam aliran dengan memanggil metode send () . Ini bisa berguna ketika mengadaptasi kode imperatif yang ada di Combine Models.

Operator Operator .




Dengan menggunakan operator, Anda dapat membuat "penerbit" Penerbit baru dari "penerbit" Penerbit lain dengan mengonversi, memfilter, dan bahkan menggabungkan nilai dari banyak Penerbit upstream sebelumnya.



Anda melihat di sini banyak nama operator yang sudah dikenal: compactMap , peta , filter , dropFirst , append .

Penerbit Yayasan Penerbit membangun Yayasan .


Apple juga memberi pengembang beberapa fungsionalitas Combine yang sudah ada dalam kerangka < Foundation , yaitu penerbit Penerbit, untuk tugas-tugas seperti mengambil data menggunakan URLSession , bekerja dengan notifikasi menggunakan Pemberitahuan , Timer, dan properti pemantauan berdasarkan KVO . Kompatibilitas bawaan ini akan sangat membantu kami mengintegrasikan kerangka kerja Combine ke dalam proyek kami saat ini.
Untuk mempelajari lebih lanjut tentang ini, lihat artikel "Tutorial kerangka kerja utama dalam Swift" .

Apa yang kita pelajari dengan Combine ?


Pada artikel ini, kita akan belajar bagaimana menggunakan framework Combine untuk mengambil data film dari situs web TMDb . Inilah yang akan kita pelajari bersama:

  • Menggunakan Masa Depan "penerbit" untuk membuat penutupan dengan Janji untuk nilai tunggal: nilai atau kesalahan.
  • Menggunakan "publisher" URLSession.datataskPublisher untuk "berlangganan" ke data data yang diterbitkan oleh UR L.
  • Menggunakan operator tryMap untuk mengonversi data data menggunakan penerbit Publisher lain.
  • Menggunakan operator decode untuk mengubah data data menjadi objek yang dapat didekodekan dan mempublikasikannya untuk transmisi ke elemen rantai berikutnya.
  • Menggunakan operator wastafel untuk "berlangganan" ke Penerbit "penerbit" menggunakan penutupan.
  • Gunakan pernyataan assign untuk “berlangganan” ke Publisher “publisher” dan berikan nilai yang disediakan untuk key Path diberikan.

Proyek awal


Sebelum kita mulai, kita harus mendaftar untuk menerima kunci API di situs web TMDb . Anda juga perlu mengunduh proyek awal dari repositori GitHub .
Pastikan Anda menempatkan kunci API Anda di kelas MovieStore di biarkan apiKey konstan.



Berikut adalah blok bangunan utama dari mana kami akan membuat proyek kami:

  • 1. Di dalam file Movie.swift adalah Model yang akan kami gunakan dalam proyek kami. Struktur root struct MoviesResponse mengimplementasikan protokol yang dapat didekodekan , dan kami akan menggunakannya saat mendekode data JSON menjadi Model. Struktur MoviesResponse memiliki properti hasil , yang juga mengimplementasikan protokol yang dapat didekodekan dan merupakan kumpulan film [Film] . Dialah yang menarik minat kita:



  • 2. Enum enumeration MovieStoreAPIError mengimplementasikan protokol Kesalahan . API kami akan menggunakan penghitungan ini untuk merepresentasikan berbagai jenis kesalahan: kesalahan pengambilan URL urlError , decodingError decoding error, dan responseError data mengambil kesalahan.



  • 3. API kami memiliki protokol MovieService dengan metode tunggal, fetchMovies (dari titik akhir: Titik akhir) , yang memilih film [Film] berdasarkan pada parameter titik akhir . Endpoint sendiri adalah penghitungan enum yang mewakili titik akhir untuk mengakses basis data TMDb untuk mengambil film seperti nowPlaying (terbaru), populer (populer), topRated (atas) dan akan datang (segera di layar).



  • 4. Kelas MovieStore adalah kelas khusus yang mengimplementasikan protokol MovieService untuk mengambil data dari situs TMDb . Di dalam kelas ini, kami menerapkan metode fetchMovies (...) menggunakan Combine .



  • 5. Kelas MovieListViewController adalah kelas ViewController utama di mana kami menggunakan metode wastafel untuk "berlangganan" ke metode pengambilan film fetchMovies (...) , yang mengembalikan "penerbit" Masa Depan , dan kemudian memperbarui tabel TableView dengan data film baru film menggunakan DiffableDataSourceSnapshot API .

Sebelum kita mulai, mari kita lihat beberapa komponen Combine dasar yang akan kita gunakan untuk API pengambilan data jarak jauh.

"Berlangganan" ke "penerbit" menggunakan wastafel dan penutupannya.


Cara termudah untuk "berlangganan" ke "penerbit" penerbit adalah dengan menggunakan wastafel dengan penutupannya, salah satunya akan dieksekusi setiap kali kita mendapatkan nilai baru, dan yang lain ketika "penerbit" selesai memberikan nilai - nilai .



Ingat bahwa di Combine, setiap "berlangganan" mengembalikan Cancellable , yang akan dihapus segera setelah kami meninggalkan konteks kami. Untuk mempertahankan "berlangganan" untuk waktu yang lebih lama, misalnya, untuk mendapatkan nilai secara tidak serempak, kita perlu menyimpan "berlangganan" di properti berlangganan1 . Ini memungkinkan kami untuk secara konsisten mendapatkan semua nilai (7,8,3,4) .

Future asynchronously “menerbitkan” nilai tunggal: nilai atau kesalahan Failure .


Dalam kerangka kerja Combine , "publisher" Future dapat digunakan untuk secara asinkron mendapatkan satu JENIS Hasil menggunakan penutupan. Penutupan memiliki satu parameter - Janji , yang merupakan fungsi TYPE (Hasil <Output, Kegagalan>) -> Void .

Mari kita lihat contoh sederhana untuk memahami bagaimana fungsi penerbit Masa Depan :



Kami menciptakan Masa Depan dengan hasil TYPE Int yang sukses dan kesalahan TYPE Never . Di dalam penutupan Future , kami menggunakan DispatchQueue.main.asyncAfter (...) untuk menunda eksekusi kode oleh 2 detik, dengan demikian mensimulasikan perilaku asinkron. Di dalam penutupan, kami mengembalikan Janji dengan hasil janji yang berhasil (.success (...)) sebagai nilai integer acak Int dalam kisaran antara 0 dan 100 . Selanjutnya, kami menggunakan dua langganan di masa mendatang - cancellable dan cancellable1 - dan keduanya memberikan hasil yang sama, meskipun nomor acak dihasilkan di dalam.
1. Perlu dicatat bahwa "penerbit" Future in Combine memiliki beberapa fitur perilaku dibandingkan dengan "penerbit" lainnya:

  • "Penerbit" Future selalu "menerbitkan" SATU nilai ( nilai atau kesalahan), dan ini menyelesaikan pekerjaannya.

  • "Penerbit" di masa depan adalah kelas ( reference type ) , tidak seperti "penerbit" lainnya, yang sebagian besar struktur struct ( value type ), dan diteruskan sebagai parameter penutupan Janji , yang dibuat segera setelah inisialisasi instance "penerbit" Future . Artinya, penutupan Janji ditransmisikan sebelum pelanggan " pelanggan " berlangganan ke contoh "penerbit" Masa Depan sama sekali. "Penerbit" Masa Depan sama sekali tidak memerlukan "pelanggan" untuk fungsinya, seperti yang dibutuhkan oleh semua Penerbit biasa lainnya. Itulah sebabnya teks "Halo dari dalam masa depan!" Hanya dicetak sekali dalam kode di atas.

  • "Penerbit" di masa depan adalah "penerbit" yang eager (tidak sabar), tidak seperti "penerbit" malas lainnya ("publikasikan" hanya jika ada "berlangganan"). Hanya sekali penerbit Masa Depan menutup Janji , hasilnya diingat dan kemudian dikirim ke "pelanggan" saat ini dan di masa depan. Dari kode di atas, kita melihat bahwa ketika Anda berulang kali "berlangganan" tenggelam ke penerbit masa depan , Anda selalu mendapatkan nilai "acak" yang sama (dalam hal ini 6 , tetapi bisa berbeda, tetapi selalu sama), meskipun digunakan pada penutupan nilai int acak.

Logika "penerbit" Future seperti itu memungkinkannya digunakan dengan sukses untuk menyimpan hasil perhitungan yang menghabiskan sumber daya yang tidak sinkron dan tidak mengganggu "server" untuk beberapa "langganan" berikutnya.

Jika logika "penerbit" Masa Depan seperti itu tidak cocok untuk Anda dan Anda ingin Masa Depan Anda disebut malas dan setiap kali Anda mendapatkan nilai Int acak baru, maka Anda harus "membungkus" Masa Depan dalam Tangguhan :



Kami akan menggunakan Future dengan cara klasik, seperti yang disarankan dalam Combine , yaitu, sebagai "penerbit" yang dihitung sebagai "penerbit" yang tidak sinkron.

Catatan 2. Kita perlu membuat satu komentar lagi tentang "berlangganan" menggunakan wastafel ke "Penerbit" asinkron. Metode wastafel mengembalikan AnyCancellable , yang tidak selalu kita ingat. Ini berarti bahwa Swift menghancurkan AnyCancellable pada saat Anda meninggalkan konteks yang diberikan, yang merupakan apa yang terjadi pada main thread . Dengan demikian, ternyata AnyCancellable dihancurkan sebelum penutupan dengan Janji dapat dimulai pada main thread . Ketika AnyCancellable dihancurkan, metode pembatalannya dipanggil, yang dalam hal ini membatalkan "berlangganan". Itulah sebabnya kita mengingat "langganan" wastafel kami ke masa depan dalam variabel yang dapat dibatalkan <dan dapat dibatalkan1 atau di Set <AnyCancellable> () .


Menggunakan Combine untuk mengambil film dari situs web TMDb .


Untuk memulainya, Anda membuka proyek starter dan pergi ke file MovieStore.swift dan metode fetchMovies dengan implementasi kosong:



Menggunakan metode fetchMovies, kita dapat memilih berbagai film dengan menetapkan nilai tertentu ke parameter input titik akhir TYPE Endpoint . TYPE Endpoint adalah penghitungan enum yang mengambil nilai nowPlaying (saat ini), akan datang (segera hadir di layar), popular (popular), topRated (atas):



Mari kita mulai dengan menginisialisasi Future dengan penutupan callback . Received Future kami akan mengembalikan uang.



Di dalam penutupan callback , kami menghasilkan URL untuk nilai yang sesuai dari parameter input titik akhir menggunakan fungsi menghasilkanURL (dengan titik akhir: Titik akhir) :



Jika URL benar tidak dapat dibuat, maka kami mengembalikan kesalahan menggunakan janji (.failure (.urlError (...)) , jika tidak, kami akan melanjutkan dan mengimplementasikan "publisher" URLSession.dataTaskPublisher .

Untuk "berlangganan" ke data dari URL tertentu URL kita dapat menggunakan metode datataskPublisher yang dibangun ke dalam kelas URLSession , yang menggunakan URL sebagai parameter dan mengembalikan "penerbit" Penerbit dengan data output tuple (data: Data, respons: URLResponse) dan kesalahan URLError .



Untuk mengonversi satu penerbit Penerbit menjadi penerbit Penerbit lainnya, gunakan operator tryMap . Dibandingkan dengan peta , operator tryMap dapat melempar kesalahan Error lemparan di dalam penutupan, yang mengembalikan penerbit Publisher baru kepada kami.

Pada langkah berikutnya, kita akan menggunakan operator tryMap untuk memeriksa kode http statusCode dari respons respons untuk memastikan nilainya antara 200 dan 300 . Jika tidak, maka kita membuang nilai error enumerasi enumerasi responseError enum MovieStoreAPIE . Jika tidak (ketika tidak ada kesalahan), kami cukup mengembalikan data data yang diterima ke Penerbit berikutnya dalam rantai “penerbit”.



Pada langkah berikutnya, kita akan menggunakan operator decode , yang menerjemahkan kode data JSON output dari "publisher" tryMap ke MovieResponse <Model menggunakan JSONDecoder .



... jsonDecoder dikonfigurasikan untuk format tanggal tertentu:



Agar pemrosesan dapat dilakukan pada main thread , kami akan menggunakan operator terima (pada :) dan meneruskan RunLoop.main sebagai parameter inputnya. Ini akan memungkinkan "pelanggan" untuk mendapatkan nilai nilai pada utas main .



Akhirnya, kami sampai pada akhir rantai transformasi kami, dan di sana kami menggunakan wastafel untuk mendapatkan langganan " berlangganan " ke "rantai" penerbit "penerbit" yang dibentuk. Untuk menginisialisasi instance kelas Sink , kita memerlukan dua hal, meskipun salah satunya adalah opsional:

  • closure acceptValue :. Ini akan dipanggil setiap kali berlangganan " berlangganan " menerima nilai baru dari Penerbit "penerbit".

  • Penutupan acceptCompletion: (Opsional). Itu akan dipanggil setelah Penerbit "penerbit" selesai menerbitkan nilai , itu akan diberi penghitungan selesai , yang dapat kita gunakan untuk memeriksa apakah "publikasi" dari nilai-nilai benar-benar selesai atau penyelesaian itu karena kesalahan.

Di dalam penutupan acceptValue , kami hanya menjalankan janji dengan opsi .success dan nilai $ 0.hasil , yang dalam kasus kami adalah serangkaian film film . Di dalam penutupan acceptCompletion, kami memeriksa apakah penyelesaian memiliki kesalahan kesalahan , kemudian meneruskan kesalahan janji yang sesuai dengan opsi .failure .



Perhatikan bahwa di sini kami mengumpulkan semua kesalahan "dibuang" di tahap sebelumnya dari "rantai penerbit".

Selanjutnya, kami menghafal langganan " berlangganan " di properti Set <AnyCancellable> () .
Faktanya adalah bahwa berlangganan "berlangganan" dibatalkan , itu adalah protokol yang menghancurkan dan menghapus semuanya setelah selesainya fungsi fetchMovies . Untuk memastikan bahwa langganan "berlangganan" dipertahankan bahkan setelah fungsi ini selesai, kita perlu mengingat langganan " berlangganan " dalam variabel eksternal ke fungsi fetchMovies . Dalam kasus kami, kami menggunakan properti langganan , yang memiliki tipe Set <AnyCancellable> () dan menggunakan metode .store (dalam: & self.subscription) , yang memastikan kegunaan "langganan" setelah fungsi fetchMovies menyelesaikan tugasnya.



Ini menyimpulkan pembentukan metode fetchMovies untuk memilih film dari database TMDb menggunakan kerangka kerja Combine . Metode fetchMovies , sebagai parameter input dari, mengambil nilai enum enpoint Endum enumerasi, yaitu, apa film tertentu yang menarik bagi kami:

.nowPlaying - film yang saat ini ada di layar,
.upcoming - film yang akan segera hadir,
.popular - film populer,
.topRated - film teratas, yaitu, dengan peringkat sangat tinggi.
Mari kita coba menerapkan API ini ke desain aplikasi dengan antarmuka pengguna UIKit yang biasa dalam bentuk Table View Controller :



dan ke aplikasi yang antarmuka pengguna dibangun menggunakan kerangka kerja SwiftUI deklaratif baru:



"Berlangganan" ke film dari film View Controller biasa.


Kami pindah ke file MovieListViewController.swift dan dalam metode viewDidLoad panggil metode fetchMovies .



Di dalam metode fetchMovies kami , kami menggunakan movieAPI yang dikembangkan sebelumnya dan metode fetchMovies dengan parameter .nowPlaying sebagai titik akhir dari dari parameter input. Artinya, kita akan memilih film-film yang saat ini ada di layar bioskop.



Metode movieAPI.fetchMovies (dari: .nowPlaying) mengembalikan Future "publisher", yang kami "berlangganan" menggunakan wastafel , dan menyediakannya dengan dua penutupan. Dalam penutupan acceptCompletion , kami memeriksa apakah ada kesalahan kesalahan dan menampilkan peringatan darurat kepada peringatan pengguna dengan pesan kesalahan ditampilkan.



Dalam penutupan acceptValue, kami memanggil metode generateSnapshot dan meneruskan film yang dipilih untuk itu .



Fungsi generateSnapshot menghasilkan NSDiffableDataSourceSnapshot baru menggunakan film kami, dan menerapkan snapshot yang dihasilkan ke diffableDataSource dari tabel kami.

Kami meluncurkan aplikasi dan menyaksikan bagaimana UIKit bekerja dengan "penerbit" dan "pelanggan" dari kerangka kerja Combine . Ini adalah aplikasi yang sangat sederhana yang tidak memungkinkan Anda untuk menyelaraskan ke berbagai koleksi film - sekarang ditampilkan di layar, populer, berperingkat tinggi atau yang akan muncul di layar dalam waktu dekat. Kami hanya melihat film-film yang akan muncul di layar ( .upcoming ). Tentu saja, Anda bisa melakukan ini dengan menambahkan elemen UI apa pun untuk mengatur nilai enumerasi Endpoint , misalnya, menggunakan Stepper atau Segmented Control , dan kemudian perbarui antarmuka pengguna. Ini sudah dikenal, tetapi kami tidak akan melakukan ini dalam aplikasi berbasis UIKit , tetapi menyerahkan ini pada kerangka kerja SwiftUI deklaratif yang baru.
Kode untuk aplikasi berbasis UIKit dapat ditemukan di Github di CombineFetchAPICompleted-UIKit .

Gunakan SwiftUI untuk menampilkan film film

.
Kami membuat aplikasi baru CombineFetchAPI-MYdengan antarmuka SwiftUI menggunakan menu File-> New-> Projectdan memilih template Single View Appdi bagian iOS:



Kemudian tentukan nama proyek dan metode pembuatan UI- SwiftUI :



Selanjutnya, tentukan lokasi proyek dan salin file Model ke proyek baru Movie.swiftdan letakkan di folder yang Modeldiperlukan untuk interaksi dengan TMDb file MovieStore.swift, MovieStoreAPIError.swiftdan MovieService.swift, dan tempat mereka sesuai dalam folder MovieServicedan Protocol:



di SwiftUI diperlukan itu adalah Codable , jika kita akan mengisi nya JSONdata, danDapat diidentifikasi , jika kita ingin membuatnya lebih mudah untuk menampilkan daftar film [Film] sebagai Daftar daftar . Model-model di SwiftUI tidak perlu Equatable dan Hashable , seperti yang disyaratkan oleh UIKit API untuk UITableViewDiffableDataSource dalam aplikasi UIKit sebelumnya . Oleh karena itu, kami menghapus dari < struktur Movie struct semua metode yang terkait dengan protokol Equatable dan Hashable :


............................


Ada artikel yang dapat diidentifikasi yang menunjukkan perbedaan dan kesamaan antar Swiftprotokol.Dapat diidentifikasi , Hashable , dan Setara .

Antarmuka pengguna yang akan kami buat menggunakan SwiftUI didasarkan pada kenyataan bahwa kami mengubah titik akhir saat mengambil data dan mendapatkan koleksi film yang kami butuhkan, yang disajikan dalam bentuk daftar:



Seperti halnya dalam kasus UIKit , data diambil sampelnya menggunakan fungsi movieAPI .fetchMovies (dari titik akhir: Titik akhir) , yang mendapatkan titik akhir yang diinginkan dan mengembalikan Masa Depan "penerbit" <[Film, MovieStoreAPIError]> . Jika kita melihat pada enumerasi Endpoint , kita akan melihat bahwa kita dapat menginisialisasi opsi yang diinginkankasus di daftar Endpoint dan sesuai koleksi film yang diinginkan dengan menggunakan indeks indeks :



Akibatnya, untuk mendapatkan yang diinginkan kami film koleksi film , cukup untuk mengatur indeks yang sesuai indexEndpoint Transfer Endpoint . Mari kita lakukan View Model, yang dalam kasus kami akan menjadi kelas MoviesViewModel yang mengimplementasikan protokol ObservableObject . Tambahkan file MoviesViewModel.swift baru untuk proyek kami View Model:



Di kelas yang sangat sederhana ini, kami memiliki dua properti @Published : satu @Published var indexEndpoint: Int- input, film var @Diterbitkan lainnya: [Film] - output. Setelah kita menempatkan @Published properti depan indexEndpoint , kita dapat mulai menggunakannya sebagai properti sederhana indexEndpoint , dan sebagai publisher $ indexEndpoint .
Saat menginisialisasi instance kelas MoviesViewModel kami , kami harus merentangkan rantai dari input "penerbit" $ indexEndpoint ke output "publisher" TYPE AnyPublisher <[Movie], Never> , yang kami dapatkan dengan menggunakan movieAPI.fetchMovies (dari: fungsi Endpoint (indeks ) yang sudah kami ketahui : indexPoint)) dan operator flatMap .



Selanjutnya, kami "berlangganan" ke "penerbit" yang baru diterima ini dengan menggunakan "pelanggan" yang sangat sederhana dengan asumsi (untuk: \ .movies, pada: self) dan menetapkan nilai yang diterima dari "penerbit" ke larik keluaran film . Kami dapat menerapkan "berlangganan" dengan asumsi (untuk: \. Film, pada: diri) hanya jika "penerbit" tidak melakukan kesalahan, yaitu, ia memiliki TYPE of error Never . Bagaimana cara mencapai ini? Menggunakan operator replaceError (with: []) , yang menggantikan kesalahan dengan array film kosong dari film .

Artinya, versi sederhana pertama aplikasi SwiftUI kami tidak akan menampilkan informasi tentang kemungkinan kesalahan kepada pengguna.

Sekarang kita punya View Modeluntuk film kita, mari kita mulai membuat UI. Menambahkan ke file ContentView.swift kami View Modelbagaimana @EnvironmentObject variabel var moviesViewModel dan mengganti teks ( «Hello, World!» ) Pada
Teks ( "\ (moviesViewModel.indexEndpoint)") , yang hanya menampilkan indeks indexEndpoint koleksi film varian.



Secara default, dalam View Modelindeks koleksi kami indexEndpoint = 2 , yaitu, ketika memulai aplikasi, kita akan melihat film yang akan muncul di layar ( Mendatang ) dalam waktu dekat :



Kemudian tambahkanUIElemen untuk mengontrol koleksi film mana yang ingin kami tampilkan. Ini adalah Stepper :



... dan Picker :



Keduanya menggunakan "penerbit" $ moviesViewModel.indexEndpoint of our kita View Model, dan dengan salah satu dari mereka (tetap, yang mana) kita dapat memilih koleksi film yang kita butuhkan:



Kemudian kita menambahkan daftar film yang diterima menggunakan Daftar dan ForEach dan atribut minimal movie itu sendiri film :



Daftar film yang ditampilkan, filmViewModel.movies yang juga kami ambil dari film kami View Model:



Kami TIDAK menggunakan "penerbit" $ moviesViewModel.movies dengan tanda $, karena kami tidak akan mengedit apa pun dalam daftar film ini. Kami menggunakan properti moviesViewModel.movies yang biasa .

Anda dapat membuat daftar film lebih menarik dengan menampilkan di setiap baris daftar poster film dari film yang sesuai, URLyang disajikan dalam struktur Film :



Kami meminjam kesempatan ini dari Thomas Ricouard dari proyeknya yang indah MovieSwiftUI .

Seperti dalam kasus memuat film , untuk gambar UIImage , kami memiliki layanan ImageService yang mengimplementasikan metode fetchImage dengan Combinemengembalikan "penerbit" AnyPublisher <UIImage?, Never> :



... dan kelas akhir ImageLoader: ObservableObject yang mengimplementasikan protokol ObservableObject dengan image properti @Published : UIImage? :



Satu-satunya persyaratan yang dibuat oleh protokol ObservableObject adalah keberadaan properti objectWillChange . SwiftUI menggunakan properti ini untuk memahami bahwa ada sesuatu yang berubah pada instance kelas ini, dan segera setelah ini terjadi, memperbarui semua Tampilan yang bergantung pada instance kelas ini. Biasanya, kompiler secara otomatis membuat properti objectWillChange , dan semua properti @Published juga secara otomatis memberitahukannya. Dalam kasus beberapa situasi eksotis, Anda bisa secara manual membuat objectWillChange dan memberi tahu perubahan. Kami hanya punya kasus seperti itu.
Di kelas ImageLoaderapakah kita memiliki gambar var properti @Published tunggal : UIImage? .Hal ini dalam pelaksanaan cerdas ini baik input dan output, Pada contoh inisialisasi ImageLoader , kita menggunakan "publisher" $ image dan "langganan" dia memanggil LoadImage () , yang banyak diperlukan kita untuk gambar poster ukuran yang tepat ukuran dan penerima itu @Diterbitkan ke properti var image: UIImage? .Kami akan memberi tahu objectWillChange tentang perubahan ini .
Kami dapat memiliki banyak gambar seperti itu dalam tabel, yang mengarah ke biaya waktu yang signifikan, jadi kami menggunakan caching instance imageLoader dari kelas ImageLoader :



Kami memiliki Pandangan khusus untuk memutar poster film MoviePosterImage :



... dan kami akan menggunakannya ketika menampilkan daftar film di ContentView utama kami. :





Kode untuk aplikasi berbasis SwiftUI tanpa tampilan kesalahan dapat ditemukan di Github di folder CombineFetchAPI-NOError.

Menampilkan kesalahan pengambilan film asinkron jarak jauh.


Sejauh ini, kami belum menggunakan atau menampilkan kesalahan yang terjadi selama pemilihan film asinkron yang jauh dari situs web TMDb . Meskipun fungsi movieAPI.fetchMovies (dari endpoint: Endpoint) yang kami gunakan memungkinkan kami melakukan ini, karena ia mengembalikan "publisher" Future <[Movie, MovieStoreAPIError]> .

Dalam rangka untuk memungkinkan kesalahan, menambah kami View Modelyang lain @Published properti moviesError: MovieStoreAPIError? yang mewakili kesalahan. Ini adalah properti Opsional , nilai awalnya adalah nihil , yang sesuai dengan tidak adanya kesalahan:



Untuk mendapatkan kesalahan ini filmErrorKita harus sedikit mengubah inisialisasi MoviesViewModel dan penggunaan lebih kompleks "Subscriber» sink :



Kesalahan moviesError dapat ditampilkan pada UI, jika tidak sama dengan nil ...



menggunakan AlertView :



Kami simulasi kesalahan ini, cukup dengan menghapus yang benar yang APIkunci:



kode aplikasi berdasarkan SwiftUI dengan tampilan kesalahan dapat ditemukan di Github di folder CombineFetchAPI-Error .

Jika awalnya Anda berencana untuk tidak menangani kesalahan, maka Anda dapat melakukannya tanpa Future <[Movie], MovieStoreAPIError> , dan mengembalikan yang biasaAnyPublisher <[Film], Never> dalam metode fetchMoviesLight :



Tidak adanya kesalahan ( Tidak Pernah ) memungkinkan kita untuk menggunakan tugas "pelanggan" yang sangat sederhana (ke: \. Film, pada: mandiri) :



Semuanya akan bekerja seperti sebelumnya:



Kesimpulan


Menggunakan kerangka kerja Combine untuk memproses urutan nilai yang muncul secara sinkron dalam waktu sangat sederhana dan mudah. Operator yang menawarkan Combine kuat dan fleksibel. Combine memungkinkan kita untuk menghindari penulisan kode asinkron yang kompleks dengan menggunakan rantai hulu penerbit Penerbit , menggunakan operator dan Pelanggan bawaan . Combine dibangun di level yang lebih rendah dari Foundation , dalam banyak kasus tidak membutuhkan Foundation dan memiliki kinerja yang luar biasa.



SwiftUI juga sangat terikat dengan Combine<terima kasih kepada @ObservableObject , @Binding, dan @EnvironmentObject .
iOSpengembang telah lama menunggu Applesemacam kerangka kerja resmi, dan akhirnya tahun ini hal itu terjadi.

Tautan:

Mengambil API Async Jauh dengan Apple Combine Framework
coba! Swift NYC 2019 - Memulai dengan Combine
"Tutorial kerangka kerja Combine terbaik di Swift".

Combine: Pemrograman Asinkron dengan

Combine Introducing Combine - WWDC 2019 - Video - Pengembang Apple. sesi 722
(sinopsis sesi 722 "Pengantar Menggabungkan" dalam bahasa Rusia)

Gabungkan dalam Praktek - WWDC 2019 - Video - Pengembang Apple. sesi 721
(Sinopsis Sesi 721 “Penggunaan Praktis Combine” dalam bahasa Rusia)

SwiftUI & Combine: Bersama-sama lebih baik. Mengapa SwiftUI dan Combine membantu Anda membuat aplikasi yang lebih baik.

MovieSwiftUI .

Visualisasikan Combine Magic dengan SwiftUI Bagian 1 ()
Visualisasikan Combine Magic dengan SwiftUI - Bagian 2 (Operator, berlangganan, dan membatalkan di Combine)
Visualize Combine Magic with SwiftUI Part 3 (See Combine Merge and Append in Action)
Visualize Combine Magic with SwiftUI — Part 4

Visualize Combine Magic with SwiftUI — Part 5

Getting Started With the Combine Framework in Swift
Transforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatest
Combine's Future
Using Combine
URLSession and the Combine framework

Source: https://habr.com/ru/post/id476678/


All Articles