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-MY
dengan antarmuka SwiftUI menggunakan menu File
-> New
-> Project
dan memilih template Single View App
di bagian iOS
:
Kemudian tentukan nama proyek dan metode pembuatan UI
- SwiftUI :
Selanjutnya, tentukan lokasi proyek dan salin file Model ke proyek baru Movie.swift
dan letakkan di folder yang Model
diperlukan untuk interaksi dengan TMDb file MovieStore.swift
, MovieStoreAPIError.swift
dan MovieService.swift
, dan tempat mereka sesuai dalam folder MovieService
dan Protocol
:
di SwiftUI diperlukan itu
adalah Codable , jika kita akan mengisi nya JSON
data, 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 Swift
protokol.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 Model
untuk film kita, mari kita mulai membuat UI
. Menambahkan ke file ContentView.swift kami View Model
bagaimana @EnvironmentObject variabel var moviesViewModel dan mengganti teks ( «Hello, World!» ) PadaTeks ( "\ (moviesViewModel.indexEndpoint)") , yang hanya menampilkan indeks indexEndpoint koleksi film varian.
Secara default, dalam View Model
indeks koleksi kami indexEndpoint = 2 , yaitu, ketika memulai aplikasi, kita akan melihat film yang akan muncul di layar ( Mendatang ) dalam waktu dekat :
Kemudian tambahkanUI
Elemen 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, URL
yang 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 Model
yang 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 API
kunci:
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 .iOS
pengembang telah lama menunggu Apple
semacam kerangka kerja resmi, dan akhirnya tahun ini hal itu terjadi.Tautan:Mengambil API Async Jauh dengan Apple Combine Frameworkcoba! Swift NYC 2019 - Memulai dengan Combine"Tutorial kerangka kerja Combine terbaik di Swift".Combine: Pemrograman Asinkron denganCombine 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 4Visualize Combine Magic with SwiftUI — Part 5Getting Started With the Combine Framework in SwiftTransforming Operators in Swift Combine Framework: Map vs FlatMap vs SwitchToLatestCombine's FutureUsing CombineURLSession and the Combine framework