Menggabungkan Data Berorientasi
Terjemahan artikel disiapkan khusus untuk siswa dari kursus lanjutan "Pengembang iOS" .
Dalam seri posting sebelumnya , kami berhasil membangun platform di atas SwiftUI, yang dengannya Anda dapat dengan bebas mengamati urutan nilai yang melewati penerbit Combine.Kami juga menciptakan serangkaian contoh menunjukkan beberapa Menggabungkan operator default yang mampu memodifikasi dan mengubah nilai-nilai dalam urutan seperti filter
sebagai map
, drop
dan scan
. Selain itu, kami telah menghadirkan beberapa operator yang menghubungkan ( Zip
dan CombineLatest
) atau menyatukan ( Merge
dan Append
) urutan.Pada titik ini, beberapa dari Anda mungkin bosan karena harus mengatur dan memelihara begitu banyak kode untuk setiap contoh (setidaknya saya sudah lelah). Lihat berapa banyak dari mereka yang ada di repositori combin-magic-swiftui di folder tutorial? Masing-masing contoh adalah representasi dari SwiftUI. Masing-masing dari mereka hanya mentransfer satu atau beberapa penerbit ke StreamView
, dan StreamView
menandatangani penerbit di klik tombol.Oleh karena itu, saya harus dapat membuat daftar penerbit di program startup dan menggunakan kembali secara terprogram StreamView
, seperti pada tangkapan layar di bawah ini.
Namun, masalah dengan solusi ini adalah skalabilitas, saat Anda harus membuat banyak penerbit.Solusi saya untuk masalah ini adalah entah bagaimana menyimpan penerbit ini. Jika saya bisa membuat serialisasi entah bagaimana caranya, saya bisa menyimpannya. Jika saya berhasil menyimpannya, saya tidak hanya dapat memodifikasinya tanpa mengubah kode, tetapi saya juga dapat membaginya dengan perangkat lain yang mendukung Combine .Penyimpanan dan transfer operator Gabungkan
Sekarang mari kita lihat tujuan kita di sini secara lebih spesifik. Karena kami memiliki daftar aliran dan operator dalam format Publisher
, kami ingin dapat menyimpannya dalam segala jenis penyimpanan - misalnya, pada hard disk atau dalam database.Jelas, kami juga harus dapat mengkonversi data yang disimpan kembali ke penerbit, tetapi selain itu, kami ingin dapat bertukar, mentransfer, dan mendistribusikan penerbit ini dengan operator dari satu tempat ke tempat lain.Setelah kami membuat struktur seperti itu, seperti yang Anda duga, dalam lingkungan terdistribusi, layanan terpusat dapat mulai mengelola logika komputasi untuk sekelompok klien.
Struktur yang Dapat Dikodekan
Jadi bagaimana kita melakukan ini? Kami akan mulai dengan mengembangkan struktur yang serializable dan deserializable. Protokol Swift Codable
memungkinkan kita melakukan ini melalui JSONEncoder
dan JSONDecoder
. Selain itu, struktur harus benar mewakili data dan perilaku untuk unit nilai terkecil dalam aliran, hingga rantai operator yang kompleks.Sebelum beralih untuk memahami komponen apa yang diperlukan untuk struktur yang akan kita buat, mari kita ingat aliran utama yang kita buat di seri posting sebelumnya .Aliran angka
Ini adalah aliran termudah; Namun, jika Anda melihat lebih dalam, Anda akan melihat bahwa ini bukan hanya urutan array. Setiap blok bundar memiliki operator penundaan sendiri ( delay
), yang menentukan waktu aktual kapan harus dikirim. Setiap nilai dalam Combine terlihat seperti ini:Just(value).delay(for: .seconds(1), scheduler: DispatchQueue.main)
Dan secara umum, semuanya terlihat seperti ini:let val1 = Just(1).delay(for: .seconds(1), scheduler: DispatchQueue.main)
let val2 = Just(2).delay(for: .seconds(1), scheduler: DispatchQueue.main)
let val3 = ....
let val4 = ....
let publisher = val1.append(val2).append(val3).append(val4)
Setiap nilai ditunda selama sedetik, dan pernyataan yang sama ditambahkan ke nilai berikutnya delay
.Karena itu, kami belajar dua hal dari pengamatan kami.- Aliran bukanlah unit terkecil dalam struktur. Yang terkecil adalah nilai stream.
- Setiap nilai stream dapat memiliki operator tanpa batas yang mengontrol kapan dan nilai apa yang dikirimkan.
Buat StreamItem Anda
Karena nilai stream dan operatornya adalah unit terkecil, kami mulai dengan membuat strukturnya. Mari kita panggil dia StreamItem
.struct StreamItem<T: Codable>: Codable {
let value: T
var operators: [Operator]
}
StreamItem
termasuk nilai stream dan array operator. Menurut persyaratan kami, kami ingin dapat menyimpan semuanya dalam struktur sehingga keduanya value
, dan StreamItem
mematuhi protokol Codable
.Nilai streaming harus universal untuk mengakomodasi semua jenis nilai.Buat StreamModel Anda
Kami akan membahas struktur untuk operator nanti. Mari kita hubungkan array StreamItem
ke StreamModel
.struct StreamModel<T: Codable>: Codable, Identifiable {
var id: UUID
var name: String?
var description: String?
var stream: [StreamItem<T>]
}
StreamModel
berisi larik StreamItem
s. StreamModel
juga memiliki properti pengidentifikasi, nama, dan deskripsi. Sekali lagi, semua yang ada di StreamModel
dalamnya harus dapat dikodekan untuk penyimpanan dan distribusi.Buat struktur operator
Seperti yang kami sebutkan sebelumnya, operator delay
dapat mengubah waktu transmisi StreamItem
.enum Operator {
case delay(seconds: Double)
}
Kami menganggap operator delay
sebagai enumerasi ( enum
) dengan satu nilai terkait untuk menyimpan waktu tunda.Tentu saja, enumerasi Operator
juga harus cocok Codable
, yang mencakup pengkodean dan dekode nilai terkait. Lihat implementasi penuh di bawah ini.enum Operator {
case delay(seconds: Double)
}
extension Operator: Codable {
enum CodingKeys: CodingKey {
case delay
}
struct DelayParameters: Codable {
let seconds: Double
}
enum CodingError: Error { case decoding(String) }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let delayParameters = try? container.decodeIfPresent(DelayParameters.self, forKey: .delay) {
self = .delay(seconds: delayParameters.seconds)
return
}
throw CodingError.decoding("Decoding Failed. \(dump(container))")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .delay(let seconds):
try container.encode(DelayParameters(seconds: seconds), forKey: .delay)
}
}
}
Kami sekarang memiliki struktur yang baik untuk mewakili aliran sekuensial ini, yang menghasilkan nilai dari 1 hingga 4 dengan interval penundaan kedua.let streamA = (1...4).map { StreamItem(value: $0,
operators: [.delay(seconds: 1)]) }
let serialStreamA = StreamModel(id: UUID(), name: "Serial Stream A",
description: nil, stream: streamA)
Ubah StreamModel menjadi Publisher
Sekarang kita telah membuat turunan dari aliran; namun, jika kami tidak mengubahnya menjadi penerbit, semuanya akan menjadi tidak berarti. Mari mencoba.Pertama-tama, setiap model operator mengacu pada operator Combine yang sebenarnya, yang harus ditambahkan ke penerbit ini dan dikembalikan ke penerbit yang dioperasikan.extension Operator {
func applyPublisher<T>(_ publisher: AnyPublisher<T, Never>) -> AnyPublisher<T, Never> {
switch self {
case .delay(let seconds):
return publisher.delay(for: .seconds(seconds), scheduler: DispatchQueue.main).eraseToAnyPublisher()
}
}
}
Saat ini hanya ada satu jenis operator - delay
. Kami akan menambahkan lebih banyak seiring berjalannya waktu.Sekarang kita dapat mulai menggunakan penerbit untuk semua orang StreamItem
.extension StreamItem {
func toPublisher() -> AnyPublisher<T, Never> {
var publisher: AnyPublisher<T, Never> =
Just(value).eraseToAnyPublisher()
self.operators.forEach {
publisher = $0.applyPublisher(publisher)
}
return publisher
}
}
Kami mulai dengan nilai Just
, menggeneralisasikannya menggunakan metode eraseToAnyPublisher
, dan kemudian menggunakan penerbit dari semua operator terkait.Pada tingkat StreamModel
kami mendapatkan penerbit dari seluruh aliran.extension StreamModel {
func toPublisher() -> AnyPublisher<T, Never> {
let intervalPublishers =
self.stream.map { $0.toPublisher() }
var publisher: AnyPublisher<T, Never>?
for intervalPublisher in intervalPublishers {
if publisher == nil {
publisher = intervalPublisher
continue
}
publisher =
publisher?.append(intervalPublisher).eraseToAnyPublisher()
}
return publisher ?? Empty().eraseToAnyPublisher()
}
}
Anda menebaknya dengan benar: kami menggunakan metode ini append
untuk menggabungkan penerbit.Visualisasi, pengeditan, dan sekali lagi visualisasi aliran
Sekarang kita cukup mendekode penerbit, mentransfer dan membuat StreamView (lihat bagaimana kami melakukannya di posting sebelumnya ). Dan yang tak kalah pentingnya: sekarang kita cukup mengedit StreamModel
, menambahkan yang lain StreamItem
dengan nilai baru dan bahkan membagikan model ini dengan perangkat lain melalui Internet.Lihat demo di bawah ini. Sekarang kita dapat membuat perubahan ke aliran tanpa mengubah kode.
Bab selanjutnya: Filter Serialisasi / Deserialisasi dan Operator Peta
Pada bagian selanjutnya, kita akan menambahkan lebih banyak operator ke enumerasi operator dan mulai menerapkannya pada level utas.Sampai nanti, Anda dapat menemukan kode sumber di sini di repositori kombinasi-sihir-swifui di folder taman bermain-gabungan.Kami menunggu komentar Anda dan diundang ke webinar terbuka dengan topik "Aplikasi-iOS di SwiftUI menggunakan Kotlin Mobile Multiplatform".