Untuk membolehkan satu perwakilan mengendalikan beberapa jenis komponen SwiftUI, jadikan kaedah panggil balik generik dan pastikan komponen tidak bergantung pada jenis konkrit perwakilan. Panduan ini membina pemilih jubin macOS yang ringkas, yang mengekalkan jenis kandungan penghantar tanpa keperluan Self rekursif.
Pilih kontrak panggil balik sebelum memilih sintaks
Tugas pembaca adalah khusus: bina komponen SwiftUI boleh guna semula yang kandungannya boleh berubah, sementara satu perwakilan menerima panggil balik pemilihan daripada setiap pengkhususan kandungan yang disokong. Demonstrasi ini menggunakan dua jubin sintetik—teks dan bulatan—serta memaparkan pengecam jubin yang dipilih.
Swift mentakrifkan pendelegasian sebagai cara berasaskan protokol untuk kelas atau struktur menyerahkan tanggungjawab kepada tika lain. Perwakilan boleh menerima tindakan bersama penghantarnya, seperti yang ditunjukkan dalam contoh pendelegasian dalam panduan bahasa. Protokol
Bagi komponen ini, kontrak yang dimaksudkan ialah: “Bagi setiap jenis kandungan yang mematuhi View, perwakilan boleh menerima jubin yang mengandungi kandungan tersebut.” Letakkan parameter jenis pada kaedah panggil balik. Fungsi generik Swift memilih argumen jenis konkritnya pada setiap panggilan, manakala jenis berkaitan ditetapkan oleh pematuhan protokol. Kedua-duanya ialah komitmen yang berbeza. Generik
| Keperluan | Bentuk antara muka yang sesuai | Akibat |
|---|---|---|
| Satu perwakilan mengendalikan setiap jenis kandungan yang disokong | Kaedah panggil balik generik | Setiap panggil balik boleh mempunyai jenis kandungan yang berbeza. |
| Setiap perwakilan mengendalikan satu jenis kandungan tetap | Jenis berkaitan | Setiap pematuhan membekalkan satu jenis kandungan. |
| Panggil balik hanya memerlukan pengecam jubin | Panggil balik yang menerima pengecam | Panggil balik tidak bergantung pada jenis pemaparan. |
Jadual ini ialah panduan reka bentuk yang diterbitkan daripada peraturan bahasa tersebut. Pilih baris pertama apabila mengekalkan penghantar bertip ialah sebahagian daripada API. Pilih baris ketiga apabila pengecam penghantar membekalkan segala yang diperlukan penerima; membawa keseluruhan komponen akan menambah kebergantungan yang tidak perlu.
Buang kebergantungan pada jenis perwakilan itu sendiri
Reka bentuk yang bergandingan rapat boleh menjadikan komponen generik terhadap perwakilannya, kemudian merujuk komponen itu sebagai Component<Self> dalam protokol perwakilan. Di sini, Self bermaksud jenis perwakilan yang akhirnya mematuhi protokol. Jenis berkaitan juga tergolong pada jenis yang mematuhi protokol itu. Pengisytiharan
Bentuk itu menyebabkan semakan panggil balik bergantung pada pengkhususan komponen yang dengan sendirinya bergantung pada perwakilan. Sama ada sesuatu pengisytiharan rekursif tertentu diterima bergantung pada pemeriksaan kekangan lengkapnya dan diagnostik pengkompil. Tidak tepat untuk mengatakan bahawa Swift menolak setiap hubungan protokol rekursif: SE-0157 menambah kekangan jenis berkaitan rekursif dalam Swift 4.1. Sokong kekangan rekursif pada jenis berkaitan
Untuk tugas yang dipilih, jenis konkrit perwakilan langsung tidak perlu muncul dalam jenis komponen. Reka bentuk di bawah memberikan Tile satu parameter generik, Content. Perwakilan tersimpannya ialah any TileDelegate, dan keperluan protokol memperkenalkan parameter Content peringkat kaedahnya sendiri. Panggil balik mengekalkan jenis kandungan penghantar tanpa menyatakannya melalui Self perwakilan.
Ejaan any secara jelas menunjukkan nilai eksistensial: nilai tersimpan itu boleh memegang tika jenis yang mematuhi protokol. SE-0335 memperkenalkan ejaan itu dalam Swift 5.6. Penggunaan eksistensial untuk perwakilan tidak memerlukan kandungan jubin dinyahjenis kepada AnyView. Perkenalkan any eksistensial
Bina pemilih jubin macOS yang lengkap
Gunakan sasaran app SwiftUI macOS dengan pengkompil Swift 6, seperti pengkompil yang disediakan bersama Xcode 16 atau lebih baharu. Tetapkan sasaran penggunaan macOS kepada 11.0 atau lebih baharu untuk storan @StateObject dalam contoh ini. Apple mendokumenkan bahawa StateObject tersedia mulai macOS 11.0. StateObject
Gantikan pengisytiharan app yang dijana dan paparan permulaan dengan satu fail sumber ini. Pastikan sasaran itu mengandungi hanya satu pengisytiharan app @main; Swift hanya membenarkan satu pendekatan titik masuk program. Pengisytiharan
import SwiftUI
import Combine
@MainActor
protocol TileDelegate: AnyObject {
func didChoose<Content: View>(_ tile: Tile<Content>)
}
@MainActor
struct Tile<Content: View>: View {
let id: String
let delegate: any TileDelegate
let content: Content
init(
id: String,
delegate: any TileDelegate,
@ViewBuilder content: () -> Content
) {
self.id = id
self.delegate = delegate
self.content = content()
}
var body: some View {
Button(action: {
delegate.didChoose(self)
}) {
VStack(spacing: 8) {
content
.frame(width: 100, height: 50)
Text("Choose \(id)")
}
.padding(8)
}
}
}
@MainActor
final class SelectionModel: ObservableObject, TileDelegate {
@Published private(set) var selection = "No tile selected"
func didChoose<Content: View>(_ tile: Tile<Content>) {
selection = "Selected: \(tile.id)"
}
}
@MainActor
struct ChooserView: View {
@StateObject private var model = SelectionModel()
var body: some View {
VStack(spacing: 16) {
HStack(spacing: 16) {
Tile(id: "text", delegate: model) {
Text("Sample")
}
Tile(id: "circle", delegate: model) {
Circle()
}
}
Text(model.selection)
}
.padding(24)
.frame(minWidth: 340, minHeight: 180)
}
}
@main
@MainActor
struct DelegateDemoApp: App {
var body: some Scene {
WindowGroup {
ChooserView()
}
}
}
Ini ialah contoh app lengkap yang merangkumi import, perwakilan, komponen, model dan titik masuk. Ia telah diperiksa tetapi tidak dikompil atau dijalankan di sini kerana persekitaran yang tersedia tidak mempunyai pengkompil Swift atau SDK macOS.
API Button Apple menerima tindakan dan label yang menghasilkan paparan. Dalam contoh ini, mengklik jubin memanggil perwakilan daripada tindakan butang; menilai body membina antara muka tanpa menghantar peristiwa pemilihan. Button
Bina dan jalankan app pada Mac. Pada mulanya, status sepatutnya menunjukkan No tile selected. Mengklik Choose text sepatutnya menghasilkan Selected: text; mengklik Choose circle sepatutnya menghasilkan Selected: circle. Ini ialah hasil yang dijangka daripada contoh, bukannya hasil pelaksanaan yang dilaporkan.
Fahami perkara yang dikekalkan oleh contoh
Jubin pertama mempunyai jenis Tile<Text>, dan yang kedua mempunyai jenis Tile<Circle>. Kedua-duanya menggunakan tika SelectionModel yang sama. Panggil balik generiknya memenuhi keperluan protokol bagi mana-mana jenis kandungan, sambil membaca sifat id yang dikongsi. Inilah manfaat praktikal meletakkan parameter generik pada kaedah.
Penerima juga boleh mengakses tile.content sebagai Content. Namun, dalam pelaksanaan generik, kekangan yang diisytiharkan hanya menjamin keupayaan View. Kod yang memerlukan operasi khusus teks memerlukan kontrak atau maklumat tambahan yang berbeza; panggil balik tidak boleh mengandaikan bahawa setiap nilai kandungan ialah Text. Ini mengikuti model generik berasaskan kekangan Swift. Generik
Komponen melaksanakan body dengan some View, mengikut corak paparan tersuai yang didokumenkan oleh Apple. Pengisytiharan View semasa turut membawa pengasingan aktor utama. Contoh ini secara jelas menandakan protokol dan jenis yang berhadapan dengan UI sebagai @MainActor supaya panggil balik dan kemas kini model berkongsi pengasingan tersebut. View
@StateObject menyediakan storan model yang stabil sepanjang hayat identiti paparan yang mengandunginya. Perubahan pada pilihan terbitan model mengemas kini paparan status yang bergantung padanya. Memindahkan pembinaan model ke dalam ungkapan terhitung biasa tidak lagi menyatakan pemilikan itu melalui StateObject. StateObject
Contoh ini menyimpan perwakilan wajib secara kuat. Modelnya hanya menyimpan rentetan pilihan, jadi graf objek yang ditunjukkan tidak mempunyai rujukan kembali kepada jubin. Jika anda kemudian menjadikan model menyimpan penghantar, semak semula pemilikan. Panduan bahasa Apple menunjukkan perwakilan terikat kelas dengan storan lemah apabila hubungan itu memerlukannya. Protokol
Apabila jenis berkaitan menjadi kontrak yang lebih baik
Katakan penerima hanya patut menerima jubin teks. Protokol alternatif boleh mengisytiharkan associatedtype Content: View dan mewajibkan func didChoose(_ tile: Tile<Content>). Ini menyatakan satu jenis kandungan bagi setiap pematuhan. Menjadikan Content jenis berkaitan utama menyediakan sintaks kekangan yang mudah; ia tidak menukar keperluan itu menjadi kaedah yang menerima setiap jenis kandungan. SE-0346 memperkenalkan jenis berkaitan utama dalam Swift 5.7. Keperluan jenis sama yang ringan untuk jenis berkaitan utama
Alternatif ini ialah versi API yang berbeza. Anda juga akan menukar sifat perwakilan komponen dan parameter pemulanya kepada eksistensial terkekang yang sepadan, seperti any FixedTileDelegate<Content>, dan menukar setiap penerima supaya membekalkan jenis berkaitan tetap serta panggil balik bukan generik yang sepadan. Pematuhan khusus teks tidak lagi boleh berkhidmat untuk jubin bulatan melalui pematuhan yang sama.
SE-0353 menambah jenis eksistensial terkekang dalam Swift 5.7, yang membolehkan jenis berkaitan utama eksistensial diikat kepada jenis yang ditentukan. Mengikat satu jenis berkaitan masih menggambarkan hubungan tetap. Ia tidak membekalkan sekumpulan pelaksanaan panggil balik untuk jenis kandungan sewenang-wenangnya. Jenis Eksistensial Terkekang
Asingkan sokongan pengkompil daripada sokongan penggunaan. SE-0353 secara jelas menyatakan perubahan masa jalan yang tidak boleh digunakan ke belakang pada keluaran OS sedia ada. Sebelum menggunakan alternatif itu pada sasaran penggunaan yang lebih lama, semak diagnostik ketersediaan pada sasaran sebenar. Contoh lengkap di atas menggunakan eksistensial perwakilan biasa dan tidak memerlukan storan eksistensial terkekang. Jenis Eksistensial Terkekang
Diagnosis penyesuaian yang gagal mengikut urutan yang berguna
- Semak skop panggil balik yang dimaksudkan. Jika satu perwakilan mesti mengendalikan jubin teks dan bulatan, saksi pematuhannya mesti mengekalkan tandatangan kaedah generik. Kaedah yang hanya menerima
Tile<Text>tidak memenuhi keperluan itu. - Semak parameter generik komponen. Untuk reka bentuk ini,
Tilebergantung padaContent; jenisnya tidak merangkumi jenis konkrit perwakilan. - Semak pengasingan aktor. Kekalkan panggil balik UI dan model boleh perhati yang ditunjukkan pada aktor utama. Panggilan daripada konteks pengasingan lain mesti menghormati sempadan itu.
- Semak lokasi peristiwa. Hantar pemilihan daripada tindakan butang, bukan semasa mengira
bodyatau memulakan paparan. - Semak pemilikan model. Kekalkan
@StateObjectdalam paparan yang mengandungi komponen dan elakkan daripada memperkenalkan tika model kedua untuk panggil balik.
Jika penyesuaian masih gagal, ringkaskannya kepada satu jubin dan satu pelaksanaan perwakilan, kemudian pulihkan jenis kandungan kedua. Urutan itu membezakan masalah saksi protokol daripada masalah penggabungan paparan. Dua jenis kandungan berbeza dalam contoh ini menyediakan semakan kontrak yang berguna: kedua-duanya mesti sampai kepada panggil balik generik yang sama tanpa penukaran atau pemadaman jenis kandungan.
Keputusan reka bentuk utama ialah tempat variasi jenis diletakkan. Bagi perwakilan yang mengendalikan setiap pengkhususan komponen, letakkan variasi itu pada kaedah panggil balik dan pastikan jenis komponen bebas daripada identiti konkrit perwakilan. API yang terhasil mengekalkan penghantar bertip sambil memastikan aliran pemilihan macOS cukup ringkas untuk diperiksa dan dikembangkan.
Sumber
- Generik — The Swift Programming Language
- Protokol — The Swift Programming Language
- Pengisytiharan — The Swift Programming Language
- Sokong kekangan rekursif pada jenis berkaitan — Swift Evolution
- Perkenalkan any eksistensial — Swift Evolution
- Keperluan jenis sama yang ringan untuk jenis berkaitan utama — Swift Evolution
- Jenis Eksistensial Terkekang — Swift Evolution
- View — Apple Developer Documentation
- StateObject — Apple Developer Documentation
- Button — Apple Developer Documentation