Untuk menerima pelengkapan bertip berbeza dalam satu skrin macOS SwiftUI, berikan setiap komponen penyesuai generik atau penutupan bertip. Swift tidak membenarkan skrin mematuhi protokol yang sama dua kali dengan jenis berkaitan yang berbeza.
Tutorial ini membina pratonton desktop kecil yang butangnya menggunakan tajuk dan bilangan lajur sintetik. Tugasnya ialah memastikan kontrak pelengkapan setiap komponen disemak oleh pengkompil sambil membolehkan satu paparan induk menerima kedua-dua nilai.
Mengapa satu jenis tidak boleh mengguna pakai kedua-dua kontrak pelengkapan?
Jenis berkaitan bagi sesuatu protokol merupakan sebahagian daripada pematuhannya. Jika protokol pelengkapan mengisytiharkan jenis berkaitan bernama Value, jenis yang mematuhinya mesti menetapkan pilihan yang konsisten untuk jenis itu. Menambah dua kaedah pelengkapan terlebih beban tidak mewujudkan dua pematuhan yang berasingan. Panduan bahasa Swift menerangkan cara jenis berkaitan memperoleh jenis konkrit apabila sesuatu protokol diguna pakai. Generik — Jenis Berkaitan.
Sebagai contoh, skrin tidak boleh mengguna pakai satu protokol pelengkapan sekali dengan Value bersamaan TitleChoice dan sekali lagi dengan Value bersamaan LayoutChoice. Memindahkan pengisytiharan itu ke dalam sambungan berasingan tidak mengubah sekatan tersebut. SE-0143 secara jelas mengekalkan larangan terhadap pematuhan berulang, termasuk pematuhan bersyarat yang syaratnya saling berasingan. Pematuhan bersyarat.
Jenis berkaitan utama tidak menghapuskan sempadan ini. Sintaks CompletionHandling<TitleChoice> menyatakan kekangan pada jenis berkaitan; ia tidak mewujudkan identiti protokol yang berasingan bagi setiap argumen. SE-0346 memperkenalkan sintaks kekangan ini dalam Swift 5.7. Keperluan jenis sama yang ringan untuk jenis berkaitan utama.
Reka bentuk yang disokong dan digunakan di bawah meletakkan pematuhan pada penyesuai generik. Hanya ada satu pengisytiharan pematuhan, dan aplikasi mencipta nilai penyesuai yang dikhususkan secara berbeza untuk kedua-dua komponen. Induk memiliki keadaan yang terhasil tanpa mengguna pakai protokol pelengkapan itu.
Reka bentuk pelengkapan manakah yang sesuai untuk komponen anda?
Gunakan panduan membuat keputusan berikut sebelum mengubah antara muka. Ini ialah saranan reka bentuk yang diperoleh daripada peraturan jenis yang didokumenkan, bukannya keperluan tambahan bahasa Swift.
| Keperluan komponen | Reka bentuk yang disarankan | Perkara yang kekal jelas |
|---|---|---|
| Satu panggilan balik, dengan antara muka yang anda kawal | Penutupan bertip | Jenis input panggilan balik |
| API sedia ada berasaskan protokol | Penyesuai generik yang mematuhi protokol | Protokol dan jenis berkaitannya |
| Satu saluran peristiwa yang membawa beberapa alternatif bernama | Enum dengan nilai berkaitan | Setiap kes peristiwa yang dikendalikan oleh penerima |
| Beberapa operasi tidak berkaitan dengan makna berbeza | Protokol bernama yang berasingan | Keperluan tersendiri bagi setiap operasi |
Untuk contoh ini, anggap komponen boleh guna semula itu sudah menjangkakan pengendali berasaskan protokol. Penyesuai generik mengekalkan sempadan tersebut dan memastikan kod pengendalian skrin kekal berdekatan dengan kemas kini keadaannya. Jika komponen hanya memerlukan panggilan balik dan tiada keperluan protokol, penutupan ialah reka bentuk yang lebih ringkas.
Kaedah pelengkapan generik akan menyatakan janji yang berbeza. Kaedah seperti complete<T>(_ value: T) mesti berfungsi untuk setiap jenis yang dibenarkan oleh kekangannya. Kaedah itu tidak menyatakan keperluan yang lebih khusus bahawa setiap komponen menghantar satu jenis hasil tertentu. Dokumentasi generik Swift menerangkan cara fungsi generik beroperasi pada jenis yang dipilih di tapak panggilannya. Generik — Fungsi Generik.
Bina penyesuai pelengkapan bertip
Gunakan sasaran aplikasi macOS SwiftUI dengan pengkompil Swift 5.7 atau lebih baharu dan sasaran penggunaan macOS 12 atau lebih baharu untuk contoh ini. Keperluan pengkompil berpunca daripada pengisytiharan jenis berkaitan utama. Sasaran penggunaan ini ialah garis dasar konservatif untuk tutorial ini, bukannya dakwaan bahawa setiap API yang ditunjukkan mula-mula muncul dalam macOS 12.
Tiga fail di bawah bersama-sama membentuk contoh lengkap. Tambahkannya pada sasaran aplikasi yang sama, dengan menggantikan titik masuk aplikasi dan paparan kandungan yang dijana supaya sasaran itu hanya mempunyai satu pengisytiharan @main. Tiada pakej, aset, fail sampel atau input luaran diperlukan.
Cipta CompletionContracts.swift dengan pengisytiharan berikut:
import SwiftUI
struct TitleChoice {
let text: String
}
struct LayoutChoice {
let columns: Int
}
@MainActor
protocol CompletionHandling<Value> {
associatedtype Value
func complete(_ value: Value)
}
@MainActor
struct CompletionAdapter<Output>: CompletionHandling {
typealias Value = Output
private let receive: @MainActor (Output) -> Void
init(_ receive: @escaping @MainActor (Output) -> Void) {
self.receive = receive
}
func complete(_ value: Output) {
receive(value)
}
}
@MainActor
struct CompletionButton<Value, Handler: CompletionHandling>: View
where Handler.Value == Value {
let title: String
let value: Value
let handler: Handler
var body: some View {
Button(action: {
handler.complete(value)
}) {
Text(title)
}
}
}
Kekangan Handler.Value == Value menghubungkan output komponen kepada input yang diterima oleh pengendali. Oleh itu, butang yang membawa TitleChoice memerlukan pengendali yang jenis berkaitannya ialah TitleChoice. Alias jenis yang dinyatakan secara jelas memudahkan pemeriksaan jenis berkaitan penyesuai.
Penyesuai menyimpan penutupannya untuk dipanggil kemudian, jadi parameternya ditandakan @escaping. Dokumentasi penutupan Swift menghendaki anotasi ini apabila penutupan boleh hidup lebih lama daripada panggilan fungsi yang menerimanya. Penutupan — Penutupan Melolos.
Protokol, penyesuai dan paparan menggunakan @MainActor, dan panggilan balik yang disimpan mengekalkan pengasingan itu dalam jenis fungsinya. Ini menjadikan konteks pelaksanaan UI yang dimaksudkan sebahagian daripada kontrak. SE-0316 mendokumenkan kedua-dua pengasingan aktor global dan jenis fungsi yang layak dengan aktor global. Aktor global.
Apple mendokumenkan Button sebagai menerima tindakan dan label. Di sini, tindakan memanggil pengendali dengan nilai komponen yang sudah bertip; Text(title) menyediakan label yang kelihatan. Butang.
Sambungkan dua jenis hasil kepada satu skrin SwiftUI
Cipta ContentView.swift. Hasil tajuk dan susun atur mempunyai destinasi keadaan yang berasingan, dan setiap penutupan penyesuai hanya mengemas kini destinasi yang sepadan.
import SwiftUI
@MainActor
struct ContentView: View {
@State private var previewTitle = "Sample Board"
@State private var columnCount = 1
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Component Preview")
.font(.headline)
Text(previewTitle)
Text("Columns: \(columnCount)")
HStack {
CompletionButton(
title: "Apply title",
value: TitleChoice(text: "Pebble Board"),
handler: CompletionAdapter<TitleChoice> { choice in
previewTitle = choice.text
}
)
CompletionButton(
title: "Apply layout",
value: LayoutChoice(columns: 3),
handler: CompletionAdapter<LayoutChoice> { choice in
columnCount = choice.columns
}
)
}
}
.padding(24)
.frame(minWidth: 420, minHeight: 180)
}
}
Cipta TypedCompletionsApp.swift sebagai satu-satunya titik masuk aplikasi:
import SwiftUI
@main
@MainActor
struct TypedCompletionsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Induk menyimpan nilai awalnya dalam sifat @State peribadi. Apple mendokumenkan bahawa SwiftUI mengurus storan ini dan mengemas kini paparan bersandar apabila nilainya berubah. Dokumentasi semasanya juga menyatakan bahawa binaan dengan Xcode 27 atau lebih baharu menggunakan makro State sebagai ganti; gaya pengisytiharan yang ditunjukkan di sini mengikut garis dasar pembungkus sifat yang lebih awal. State.
WindowGroup mengehoskan hierarki paparan skrin. Apple mendokumenkan storan keadaan yang berasingan bagi setiap tetingkap yang dicipta oleh kumpulan itu, jadi keadaan contoh ini dimiliki oleh setiap tetingkap pratonton. WindowGroup.
Terdapat dua jenis penyesuai khusus: CompletionAdapter<TitleChoice> dan CompletionAdapter<LayoutChoice>. Kedua-duanya datang daripada satu pengisytiharan penyesuai generik. ContentView hanya mematuhi View; ia menerima kedua-dua hasil melalui penutupan yang dibekalkannya.
Sahkan tingkah laku dan diagnosis ketidakpadanan
Tiada pengkompil Swift dipasang dalam persekitaran penyelidikan, jadi fail ini telah diperiksa tetapi tidak dikompil atau dijalankan. Bina sasaran aplikasi pada Mac anda, kemudian gunakan pemeriksaan berikut untuk mengesahkan tingkah laku yang dimaksudkan bagi contoh ini.
- Lancarkan aplikasi. Pratonton sepatutnya memaparkan
Sample BoarddanColumns: 1. - Klik
Apply title. Tajuk sepatutnya menjadiPebble Board, sementara bilangan lajur kekal sama. - Klik
Apply layout. Bilangannya sepatutnya menjadiColumns: 3, sementara tajuk yang dipilih kekal sama. - Klik mana-mana butang sekali lagi. Butang itu sepatutnya menggunakan nilai sintetik yang sama sekali lagi.
Untuk memeriksa sempadan jenis, ubah pengkhususan pengendali butang pertama buat sementara waktu kepada CompletionAdapter<LayoutChoice> dan berikan penutupannya badan kosong. Kekalkan nilai butang itu sebagai TitleChoice. Komponen itu kini memerlukan jenis yang tidak serasi untuk menjadi sama melalui Handler.Value == Value, maka kompilasi sepatutnya gagal. Pulihkan pengendali asal selepas itu.
Jika pengkompil melaporkan pematuhan berulang, cari pengisytiharan lain yang menyebabkan jenis nominal yang sama mengguna pakai CompletionHandling. Kekalkan satu pematuhan penyesuai generik dan cipta nilai penyesuai lain untuk output tambahan. Dua sambungan terkekang yang mengisytiharkan pematuhan yang sama tetap tertakluk pada sekatan pematuhan berulang Swift. Pematuhan bersyarat — Pematuhan berbilang.
Jika pengkompil menolak sintaks jenis berkaitan utama protokol, semak versi pengkompil. Untuk pengkompil yang lebih lama, reka bentuk penyesuai boleh mengekalkan pengisytiharan jenis berkaitan biasa. Ini ialah fragmen pengganti untuk pengisytiharan protokol sahaja; kekalkan pengisytiharan lain tanpa perubahan:
@MainActor
protocol CompletionHandling {
associatedtype Value
func complete(_ value: Value)
}
Perubahan itu menghapuskan kebergantungan contoh ini pada sintaks Swift 5.7. Ia tidak menghapuskan keperluan pengkompil untuk @MainActor, yang diperkenalkan dalam Swift 5.5, dan tidak mengubah sasaran penggunaan tutorial. Komponen itu sudah menggunakan klausa generik where, jadi ia tidak memerlukan sintaks jenis berkaitan utama di tempat penggunaannya. Keperluan jenis sama yang ringan, Aktor global.
Apakah jaminan yang diberikan oleh kontrak pelengkapan ini?
Contoh ini menjamin jenis muatan yang sepadan pada sempadan setiap komponen. Pelaksanaannya memanggil pengendali sekali bagi setiap pengaktifan butang, secara segerak. Ia tidak melaksanakan operasi tak segerak, pembatalan, kegigihan atau kitar hayat pelengkapan sekali sahaja. Semua itu memerlukan tingkah laku tambahan dan kontrak yang jelas.
Contoh ini juga menggunakan nilai setempat yang diketahui. Jika komponen pada masa hadapan menerima bilangan lajur sewenang-wenangnya, sahkan input itu sebelum mengemas kini pratonton dan tetapkan cara penolakan dilaporkan. Jenis Swift yang sepadan sahaja tidak memastikan bahawa sesuatu integer ialah bilangan lajur yang berguna.
Untuk tugas desktop ini, gunakan satu penyesuai bagi setiap titik akhir bertip dan biarkan induk memiliki keadaan yang terhasil. Dengan cara itu, kedua-dua komponen mempunyai kontrak pelengkapan yang tepat melalui satu pengisytiharan pematuhan boleh guna semula, sementara destinasi setiap hasil jelas kelihatan di tapak panggilan.
Sumber
- Pematuhan bersyarat — Swift Evolution
- Keperluan jenis sama yang ringan untuk jenis berkaitan utama — Swift Evolution
- Generik — Bahasa Pengaturcaraan Swift
- Penutupan — Bahasa Pengaturcaraan Swift
- Aktor global — Swift Evolution
- Butang — Dokumentasi Pembangun Apple
- State — Dokumentasi Pembangun Apple
- WindowGroup — Dokumentasi Pembangun Apple