Untuk menjejaki teks yang ditambah pada rentetan Swift, simpan ofset dalam pengekodan yang dinamakan dan bukannya mengekalkan indeks rentetan lama. Panduan ini membina lejar segmen UTF-16 sahaja-tambah untuk pengiraan pratonton teks Mac setempat yang ringkas.
Tugas ini khusus: gabungkan cebisan teks sintetik, ingat tempat setiap cebisan berada, dan dapatkan semula kandungan tepatnya selepas penambahan seterusnya. Contoh ini beroperasi sepenuhnya dalam ingatan dan mencetak hasilnya. Sempadan segmennya menerangkan sumbangan kepada teks, yang mungkin berbeza daripada sempadan antara aksara yang dipaparkan.
Mengapa menyimpan indeks hujung lama tidak boleh diharap
Satu pendekatan yang menggoda ialah menyimpan text.endIndex, menambah cebisan, kemudian memadankan indeks tersimpan itu dengan text.endIndex yang baharu. Ini mencampurkan indeks yang diperoleh sebelum pengubahan dengan rentetan yang diperoleh selepasnya. Dokumentasi koleksi Swift memberi amaran secara jelas bahawa operasi pengubahan boleh membatalkan indeks yang disimpan. Keputusan yang berjaya dengan sampel pendek tidak membuktikan adanya jaminan umum untuk pengubahan seterusnya. Lihat dokumentasi Collection Apple.
Ada satu lagi isu walaupun anda membina semula indeks: penambahan teks boleh mengubah sempadan aksara pada titik sambungan. Panduan bahasa Swift menunjukkan hal ini dengan "cafe" diikuti "\u{0301}", iaitu aksen akut gabungan. Teks yang terhasil masih mengandungi empat nilai Character Swift kerana aksen itu bergabung dengan huruf sebelumnya. Lihat Strings and Characters.
Oleh itu, merekod bilangan aksara lama dan bergerak ke hadapan sebanyak itu aksara dalam rentetan yang telah siap tidak dapat mendapatkan semula permulaan cebisan yang ditambah dengan pasti. Dalam contoh aksen itu, sumbangan yang ditambah bermula di dalam gugusan grafem akhir. Tiada aksara paparan berasingan yang sepadan dengan sumbangan tersebut.
Anggap sempadan penambahan dan sempadan aksara sebagai dua konsep berasingan. Yang pertama merekod cara teks itu dibina; yang kedua menerangkan cara Swift mengelompokkan teks yang telah dibina untuk operasi pada peringkat aksara.
Koordinat manakah yang patut disimpan oleh lejar?
Untuk pengiraan Mac ini, gunakan ofset dan panjang integer UTF-16. Apple mendokumenkan bahawa NSString menyatakan kedudukan dan julat rentetannya dalam unit kod UTF-16. Paparan String.utf16 Swift mendedahkan unit kod yang sepadan, jadi lejar boleh menggunakan satu sistem koordinat bagi pengukuran dan pengekstrakan subrentetan Foundation. Lihat NSString dan paparan UTF-16 String.
| Tugas pembaca | Perwakilan yang sesuai | Syarat |
|---|---|---|
| Dapatkan semula setiap cebisan asal secara berasingan | Tatasusunan nilai String milik sendiri |
Koordinat teks gabungan tidak diperlukan |
| Cari sumbangan tepat dalam teks gabungan | Ofset dan panjang UTF-16 | Teks terdahulu kekal tidak berubah |
| Bekerja dengan aksara yang dipaparkan | Indeks aksara baharu dalam rentetan semasa | Sempadan segmen perlu dinilai semula sebagai sempadan grafem |
| Kekalkan kedudukan sepanjang pengeditan arbitrari | Model kedudukan yang peka terhadap pengeditan | Lejar sahaja-tambah ini tidak mencukupi |
Lejar di bawah mempunyai empat peraturan yang jelas:
- Ia bermula dengan rentetan kosong.
- Satu-satunya pengubahan teksnya ialah menambah
StringSwift yang lengkap. - Setiap penambahan merekod jumlah UTF-16 sebelumnya dan panjang UTF-16 cebisan itu.
- Nombor segmen hanya bermakna bersama tika lejar atau syot kilat yang menghasilkannya.
Peraturan ini menjadikan ofset tersebut berguna. Menambah akhiran mengekalkan kandungan berkod sebelumnya, jadi rentang terdahulu kekal di lokasinya. Menyisip, membuang, mengganti atau mengubah teks terdahulu memerlukan kontrak yang berbeza. Reka bentuk ini berasaskan perwakilan rentetan yang didokumenkan, bukannya ciri penjejakan kedudukan automatik yang disediakan oleh Swift.
Bina lejar segmen setempat yang lengkap
Gunakan sasaran baris perintah Swift macOS dengan Foundation tersedia. Gantikan program peringkat atas sampelnya dengan program lengkap di bawah. Pelaksanaannya menggunakan append(contentsOf:), yang tandatangan Swift terdokumennya menerima satu lagi String. Lihat rujukan append(contentsOf:) Apple.
import Foundation
struct Segment: Sendable {
let utf16Location: Int
let utf16Length: Int
var foundationRange: NSRange {
NSRange(location: utf16Location, length: utf16Length)
}
}
enum LedgerError: Error {
case unknownSegment(Int)
}
struct SegmentLedger: Sendable {
private(set) var text = ""
private(set) var segments: [Segment] = []
private var utf16Total = 0
@discardableResult
mutating func append(_ fragment: String) -> Int {
let length = fragment.utf16.count
let (newTotal, overflow) =
utf16Total.addingReportingOverflow(length)
precondition(!overflow, "UTF-16 total exceeds Int capacity")
let segment = Segment(
utf16Location: utf16Total,
utf16Length: length
)
text.append(contentsOf: fragment)
segments.append(segment)
utf16Total = newTotal
return segments.count - 1
}
func fragment(at segmentID: Int) throws -> String {
guard segments.indices.contains(segmentID) else {
throw LedgerError.unknownSegment(segmentID)
}
let segment = segments[segmentID]
if segment.utf16Length == 0 {
return ""
}
return (text as NSString).substring(
with: segment.foundationRange
)
}
}
let inputs = ["cafe", "\u{0301}", "", "\u{1F7E6}", " done"]
var ledger = SegmentLedger()
for input in inputs {
ledger.append(input)
}
let expectedLocations = [0, 4, 5, 5, 7]
let expectedLengths = [4, 1, 0, 2, 5]
for id in inputs.indices {
let recovered = try ledger.fragment(at: id)
let segment = ledger.segments[id]
// Compare code units, so the check is encoding-exact.
precondition(Array(recovered.utf16) == Array(inputs[id].utf16))
precondition(segment.utf16Location == expectedLocations[id])
precondition(segment.utf16Length == expectedLengths[id])
print("\(id): location=\(segment.utf16Location), " +
"length=\(segment.utf16Length)")
}
precondition(ledger.text.utf16.count == 12)
precondition(ledger.text.count == 10)
// Later appends must preserve earlier segment contents.
ledger.append("!")
for id in inputs.indices {
let recovered = try ledger.fragment(at: id)
precondition(Array(recovered.utf16) == Array(inputs[id].utf16))
}
// Invalid segment numbers produce an explicit error.
do {
_ = try ledger.fragment(at: -1)
preconditionFailure("Expected an unknown-segment error")
} catch LedgerError.unknownSegment(let id) {
precondition(id == -1)
}
print("All segment checks passed.")
Program ini telah disemak berdasarkan dokumentasi API yang diperoleh; ia tidak dijalankan di sini kerana shell yang tersedia tidak mempunyai boleh laku Swift. Prasyarat yang disertakan menjadikan tingkah laku sampel yang dijangka jelas apabila anda menjalankannya pada Mac anda.
Perkara yang direka untuk diperiksa oleh sampel
Koordinat segmen yang dijangka dikira daripada input literal. Aksen gabungan menyumbang satu unit kod UTF-16, cebisan kosong menyumbang sifar, dan petak biru menyumbang dua. Akhiran terakhir menyumbang lima. Ini ialah pengiraan input ilustratif, bukannya ukuran prestasi.
| Segmen | Input | Lokasi UTF-16 | Panjang UTF-16 |
|---|---|---|---|
| 0 | "cafe" |
0 | 4 |
| 1 | "\u{0301}" |
4 | 1 |
| 2 | "" |
5 | 0 |
| 3 | "\u{1F7E6}" |
5 | 2 |
| 4 | " done" |
7 | 5 |
Cebisan kosong menerima nombor segmennya sendiri walaupun berkongsi lokasi dengan segmen berikutnya. Ini mengekalkan sejarah penambahan tanpa mereka-reka teks. Memulangkan "" secara eksplisit juga mengelakkan panggilan subrentetan Foundation untuk kes ini.
Pemeriksaan pemulihan sengaja membandingkan tatasusunan UTF-16. Kesamaan rentetan Swift mengenali kesetaraan kanonik, bermakna sesetengah rentetan dengan jujukan skalar asas yang berbeza dibandingkan sebagai sama. Pengesahan yang tepat mengikut pengekodan lebih sesuai dengan kontrak lejar ini. Lihat perbincangan perbandingan rentetan dalam Strings and Characters.
Mengapa pengekstrakan menggunakan Foundation
Integer yang disimpan ditukar menjadi NSRange dan dihantar terus kepada NSString.substring(with:). Apple mendokumenkan bahawa kaedah ini mengekstrak julat yang ditentukan dan menghendaki julat itu kekal dalam sempadan penerima. Julat yang tidak sah boleh menyebabkan pengecualian. Lejar mengawal kedua-dua teks dan rentangnya, serta menolak nombor segmen yang tidak diketahui sebelum pengekstrakan. Lihat substring(with:).
Setiap sempadan yang direkod berasal daripada penambahan rentetan Swift yang lengkap. Contoh ini tidak pernah mencipta ofset di tengah pasangan pengganti UTF-16 petak biru. Namun, sempadan masih boleh terletak di dalam jujukan aksara tergubah, seperti yang ditunjukkan oleh aksen itu; mendapatkan semula sumbangan tepat ialah tujuan perwakilan ini.
Jangan gunakan String.count untuk membina julat Foundation ini. Apple mentakrifkan NSString.length sebagai bilangan unit kod UTF-16, iaitu ukuran yang berbeza daripada bilangan aksara dan tidak menerangkan lebar paparan. Lihat NSString.length.
Pastikan pengubahan, penciptaan segmen dan syot kilat kekal bersama
Untuk pengiraan segerak yang ringkas, jenis nilai ini memadai: seorang pemilik menambah cebisan dan membaca hasilnya. Penetap peribadinya menghalang pemanggil daripada menggantikan teks gabungan secara berasingan daripada jadual segmen. Jumlah UTF-16 yang dicache juga mengelakkan pengiraan semula rentetan gabungan yang semakin panjang pada setiap penambahan.
Jika beberapa tugas tak segerak perlu menyumbang kepada satu lejar, letakkan lejar itu di sebalik satu aktor, atau kekalkannya pada @MainActor jika ia sebahagian daripada antara muka Mac. Lakukan penambahan dan pengemaskinian metadata dalam satu kaedah terasing yang segerak. Swift mendokumenkan bahawa kerja segerak yang terasing kepada aktor tidak boleh diganggu pada titik penggantungan dalam kaedah tersebut. Lihat Concurrency.
Pulangkan teks dan metadata segmen bersama-sama sebagai satu nilai syot kilat. Elakkan membuat permintaan baca teks dan baca jadual segmen secara berasingan: penambahan lain boleh berlaku antara panggilan tersebut. Pengisytiharan Sendable dalam contoh menerangkan nilai yang sesuai merentasi sempadan pengasingan; ia tidak menyirikan penulisan serentak pada pemboleh ubah kongsi. Dokumentasi Sendable Swift menerangkan perbezaan antara nilai yang boleh dipindahkan dengan keadaan boleh ubah yang dilindungi.
Jika tertib sumbangan penting, tetapkan tertib itu sebelum membuat penambahan. Aktor melindungi ketekalan dalaman lejar, sementara aplikasi masih perlu menentukan cebisan mana yang didahulukan.
Ketahui bila ofset ini tidak lagi memenuhi tugas
Gunakan urutan diagnostik ini sebelum menggunakan semula rentang:
- Semak pemilikan. Adakah nombor segmen milik lejar ini atau salah satu keturunannya yang serasi dan sahaja-tambah?
- Semak sejarah pengubahan. Adakah apa-apa berubah sebelum rentang yang direkod? Jika ya, bina semula lejar atau gunakan model yang peka terhadap pengeditan.
- Semak unit. Adakah koordinat pengguna dalam unit kod UTF-16? Jangan gantikan dengan bilangan aksara atau bilangan bait UTF-8.
- Semak hasil yang dimaksudkan. Adakah anda mendapatkan semula sumbangan tepat yang ditambah, atau memilih satu aksara paparan yang lengkap? Kedua-duanya mungkin memerlukan sempadan yang berbeza.
Pilihan berorientasikan aksara tidak semestinya dapat mewakili satu sumbangan yang ditambah sahaja. Aksen itu tergolong pada aksara paparan yang sama dengan huruf sebelumnya. Jika tugas pada desktop ialah penyerlahan, pilih peraturan paparan yang jelas: luaskan pilihan hingga merangkumi aksara yang berkaitan, atau paparkan sumbangan itu secara berasingan dalam senarai segmen. Peluasan merangkumi teks daripada segmen terdahulu, jadi kekalkan ofset asal untuk pemulihan tepat.
Untuk penjejakan penambahan yang tepat, invarian yang berguna adalah mudah: kekalkan awalan tanpa perubahan, namakan unit koordinat, dan simpan setiap rentang bersama teksnya. Dengan kontrak itu, lejar boleh mendapatkan semula sumbangan terdahulu selepas penambahan seterusnya tanpa membawa satu String.Index merentasi pengubahan.
Sumber
Collection — Apple.
Strings and Characters — Swift.org.
String — Apple.
NSString — Apple.
append(contentsOf:) — Apple.
substring(with:) — Apple.
length — Apple.
Concurrency — Swift.org.
Sendable — Swift.org.