Untuk memastikan model Codable Swift serasi dengan perwakilan dalam memori yang lebih lama, jadikan nilai yang benar-benar tiada sebagai pilihan dan laksanakan sandaran penyahkodan secara jelas. Menambah nilai lalai pada pengisytiharan sahaja tidak menjadikan penyahkodan medan berkod yang tiada berjaya.

Panduan ini ditujukan kepada pembangun Mac yang mengemas kini model susun atur desktop yang ringkas. Tugas khususnya ialah membolehkan model yang disemak menerima perwakilan sintetik yang lebih lama sambil mengekalkan nilai yang dibekalkan dan melaporkan jenis yang tidak serasi. Setiap contoh mencipta dan menggunakan nilai dalam memori.

Apakah perubahan apabila medan dijadikan pilihan atau diberikan nilai lalai?

Codable menggabungkan pengekodan dan penyahkodan. Swift boleh mensintesis pelaksanaan tersebut apabila sifat tersimpan menyokong protokol yang diperlukan. Ini menghapuskan kod rutin, tetapi perubahan yang masih berjaya dikompil mungkin mengubah perwakilan yang boleh diterima oleh model. Apple mendokumenkan kedua-dua pematuhan automatik dan pelaksanaan tersuai dalam Pengekodan dan Penyahkodan Jenis Tersuai.

Pertimbangkan jubin susun atur yang mempunyai tajuk dan sari kata wajib. Kini anda mahu sari kata itu menjadi pilihan dan bilangan lajur baharu mempunyai nilai lalai tiga. Ini ialah dua keputusan berasingan: sari kata mungkin tiada nilai, manakala bilangan lajur mesti sentiasa mempunyai nilai yang boleh digunakan selepas penyahkodan.

Bagi sifat boleh ubah biasa yang disertakan dalam penyahkodan tersintesis, Swift menganggap sifat pilihan membenarkan kunci tiada atau nilai null. Pemula pengisytiharan bukan sandaran penyahkodan: sifat pilihan yang dimulakan dengan nilai bukan nil masih boleh dinyahkod sebagai nil, dan sifat bukan pilihan yang mempunyai nilai lalai pengisytiharan masih boleh gagal apabila kuncinya tiada. Penjelasan pasukan Swift tentang sintesis Codable menghuraikan tingkah laku yang disengajakan ini.

Jadual keputusan berikut menterjemahkan peraturan tersebut kepada dasar medan. Andaikan medannya jenis integer dan nilai yang tersedia boleh ditukar kepada Int. Dua baris terakhir memerlukan penyahkodan tersuai.

Dasar medan Kunci tiada Null eksplisit Integer sah Jenis tidak serasi
var columns: Int tersintesis Ralat Ralat Gunakan nilai Ralat
var columns: Int = 3 tersintesis Ralat Ralat Gunakan nilai Ralat
var columns: Int? tersintesis nil nil Gunakan nilai Ralat
Sandaran tersuai untuk ketiadaan dan null 3 3 Gunakan nilai Ralat
Sandaran tersuai untuk ketiadaan sahaja 3 Ralat Gunakan nilai Ralat

Perbezaannya berpunca daripada kaedah kontena. decode(_:forKey:) memerlukan entri dan nilai bukan null yang boleh ditukar. decodeIfPresent(_:forKey:) memulangkan nil jika kunci tiada atau nilainya null, tetapi tetap melontarkan ralat untuk jenis yang tidak serasi. Penggunaan ?? 3 pada hasil yang kedua memberikan nilai sandaran.

Laksanakan dan semak dasar keserasian dalam memori

Gunakan contoh ini dalam konteks pelaksanaan Swift pada Mac anda dengan Foundation tersedia dan sokongan untuk pernyataan peringkat atas, seperti playground sedia ada. Jalankan seluruh blok sekali gus. JSONEncoder Foundation menghasilkan perwakilan Data, manakala JSONDecoder mencipta nilai bertip baharu daripadanya. Contoh ini tidak menggunakan dokumen berkod literal atau input luaran.

Dasar yang dipilih adalah jelas: title kekal wajib; subtitle yang tiada atau null menjadi nil; columns yang tiada atau null menjadi tiga. Nilai lajur yang tersedia tetapi tidak serasi kekal sebagai ralat. Pemula tersuai mengendalikan penyahkodan, manakala pengekodan kekal tersintesis berdasarkan CodingKeys model. Apple mendokumenkan pendekatan penyesuaian ini dalam Pengekodan dan Penyahkodan Jenis Tersuai.

Ini ialah program lengkap, termasuk lekapan sintetik dan semakan. Swift tidak tersedia dalam persekitaran penyelidikan, jadi contoh ini disemak berdasarkan API yang didokumenkan tetapi tidak dijalankan.

import Foundation

struct LegacyTile: Codable {
    var title: String
    var subtitle: String
}

struct Tile: Codable, Equatable {
    static let defaultColumns = 3

    var title: String
    var subtitle: String?
    var columns: Int = Tile.defaultColumns

    enum CodingKeys: String, CodingKey {
        case title, subtitle, columns
    }
}

extension Tile {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decode(String.self, forKey: .title)
        subtitle = try values.decodeIfPresent(String.self, forKey: .subtitle)
        columns = try values.decodeIfPresent(Int.self, forKey: .columns)
            ?? Self.defaultColumns
    }
}

struct MinimalTile: Encodable {
    var title: String
}

struct NullTile: Encodable {
    enum CodingKeys: String, CodingKey {
        case title, subtitle, columns
    }

    func encode(to encoder: Encoder) throws {
        var values = encoder.container(keyedBy: CodingKeys.self)
        try values.encode("Circle", forKey: .title)
        try values.encodeNil(forKey: .subtitle)
        try values.encodeNil(forKey: .columns)
    }
}

struct WrongColumnsTile: Encodable {
    var title: String
    var columns: String
}

let encoder = JSONEncoder()
let decoder = JSONDecoder()

// An older representation keeps its subtitle and gains the fallback.
let legacyBytes = try encoder.encode(
    LegacyTile(title: "Circle", subtitle: "Blue")
)
let upgraded = try decoder.decode(Tile.self, from: legacyBytes)
precondition(upgraded.title == "Circle")
precondition(upgraded.subtitle == "Blue")
precondition(upgraded.columns == 3)

// Missing keys and explicit nulls follow the chosen policy.
let minimalBytes = try encoder.encode(MinimalTile(title: "Circle"))
let minimal = try decoder.decode(Tile.self, from: minimalBytes)
precondition(minimal.subtitle == nil)
precondition(minimal.columns == 3)

let nullBytes = try encoder.encode(NullTile())
let explicitNull = try decoder.decode(Tile.self, from: nullBytes)
precondition(explicitNull == minimal)

// A supplied nondefault value survives an encode/decode cycle.
let supplied = Tile(title: "Square", subtitle: "Green", columns: 5)
let suppliedBytes = try encoder.encode(supplied)
let restored = try decoder.decode(Tile.self, from: suppliedBytes)
precondition(restored == supplied)

// Incompatible types must remain visible as decoding failures.
let wrongBytes = try encoder.encode(
    WrongColumnsTile(title: "Circle", columns: "wide")
)
do {
    _ = try decoder.decode(Tile.self, from: wrongBytes)
    preconditionFailure("Expected a type mismatch")
} catch DecodingError.typeMismatch(_, let context) {
    precondition(context.codingPath.last?.stringValue == "columns")
}

// A newer representation may no longer satisfy an older reader.
let currentBytes = try encoder.encode(minimal)
do {
    _ = try decoder.decode(LegacyTile.self, from: currentBytes)
    preconditionFailure("Expected the required subtitle to be missing")
} catch DecodingError.keyNotFound(let key, _) {
    precondition(key.stringValue == "subtitle")
}

print("All in-memory compatibility checks passed.")

Output akhir yang dijangka ialah All in-memory compatibility checks passed. Ralat yang tidak dijangka akan disebarkan dan bukannya ditukar menjadi kejayaan. Lekapan ini sengaja merangkumi perwakilan yang berbeza: MinimalTile menggugurkan medan, manakala NullTile menggunakan encodeNil(forKey:) untuk mencipta entri null yang jelas.

Mengekalkan init(from:) dalam sambungan juga membolehkan pemula memberwise struktur itu kekal tersedia, lalu contoh boleh membina Tile(title:subtitle:columns:). Pemula penyahkodan tersuai menetapkan setiap sifat tika tersimpan, jadi hasilnya dimulakan sepenuhnya sebelum dipulangkan.

Patutkah null menggunakan nilai lalai atau kekal sebagai ralat?

Pilih berdasarkan makna medan tersebut. Bagi susun atur contoh ini, menganggap ketiadaan dan null sebagai bilangan lajur piawai ialah dasar yang munasabah. Namun, ini tetap keputusan aplikasi: decodeIfPresent sengaja menggabungkan kedua-dua keadaan tersebut, lalu nilai sandaran memberikan hasil yang sama kepada kedua-duanya. Tingkah laku pulangan yang didokumenkan menjelaskan kesan ini.

Jika ketiadaan bermaksud “gunakan susun atur piawai” tetapi null yang dibekalkan sepatutnya gagal, gantikan hanya penetapan columns di dalam init(from:) program dengan petikan ini. Semua prasyarat dan takrifan lain kekal sama.

// Replacement fragment inside Tile.init(from:).
if values.contains(.columns) {
    columns = try values.decode(Int.self, forKey: .columns)
} else {
    columns = Self.defaultColumns
}

contains(_:) melaporkan sama ada entri wujud, termasuk entri null. Oleh itu, null eksplisit memasuki cabang decode dan menghasilkan valueNotFound; hanya ketiadaan menerima nilai lalai. Dengan dasar yang disemak ini, semakan lekapan null dalam program asal mesti diubah untuk menjangkakan ralat itu, bukan kesamaan dengan minimal.

Elakkan menukar penetapan itu menjadi (try? values.decode(Int.self, forKey: .columns)) ?? 3 melainkan memang dimaksudkan untuk mengetepikan setiap kegagalan penyahkodan. Ungkapan itu juga menggantikan nilai tiga bagi nilai yang dibekalkan tetapi tidak serasi. Penggunaan try decodeIfPresent mengekalkan perbezaan antara pengguguran yang diterima dengan perwakilan yang tidak dapat ditafsir oleh medan, seperti yang dinyatakan dalam kontrak ralat kaedah penyahkodan.

Adakah keserasian berfungsi dalam kedua-dua arah?

Semak setiap pembaca secara berasingan. Program ini menunjukkan bahawa pembaca yang disemak menerima perwakilan yang lebih lama. Ia juga menunjukkan satu batasan: pembaca lama masih memerlukan subtitle, jadi pembaca itu menolak perwakilan baharu apabila medan tersebut digugurkan. Menukar sifat Swift baharu kepada pilihan tidak mengubah keperluan jenis lama.

Pengekodan pilihan tersintesis menggugurkan nilai nil melalui pengekodan bersyarat. Swift mendokumenkan bahawa encodeIfPresent(_:forKey:) hanya mengekodkan nilai bukan nil. Anda boleh mengekodkan null eksplisit menggunakan encodeNil, tetapi itu masih tidak dapat memenuhi sifat String bukan pilihan yang memerlukan nilai dalam pembaca lama.

Apabila pembaca lama mesti terus boleh digunakan, takrifkan perwakilan eksplisit yang memenuhi keperluannya. Dalam contoh sintetik ini, penyesuai boleh membina LegacyTile dengan sari kata pilihan sebelum pengekodan. Sama ada sari kata yang tiada patut menjadi rentetan tertentu ialah keputusan produk yang berasingan; jangan mereka-reka makna itu secara senyap dalam sandaran umum.

Bezakan juga pengekalan nilai daripada pengekalan perwakilan. Dalam contoh ini, sari kata yang digugurkan dan null eksplisit kedua-duanya menjadi nilai Swift yang sama, nil. Oleh itu, model tidak dapat menghasilkan semula input yang diterimanya. Begitu juga, penyahkodan bilangan lajur yang tiada sebagai tiga lalu mengekodkan semula model akan membekalkan bilangan lajur yang konkrit. Ini ialah kesan langsung daripada model dan dasar penyahkodan yang dipilih.

Apakah yang patut anda sahkan sebelum menukar model?

Gunakan matriks keserasian kecil setiap kali medan berubah. Mulakan dengan perwakilan lama, kemudian ubah satu medan pada satu masa. Dengan cara ini, proses pergi balik yang berjaya tidak menyembunyikan kegagalan yang hanya berlaku apabila kunci tiada.

  • Input lama: Pastikan pembaca yang disemak menerimanya dan mengekalkan nilai sedia ada.
  • Medan tiada: Pastikan hasilnya ialah nilai pilihan, nilai sandaran atau ralat yang dimaksudkan.
  • Null eksplisit: Ujinya secara berasingan daripada pengguguran.
  • Nilai dibekalkan: Gunakan nilai bukan lalai untuk membuktikan penyahkodan tidak menimpanya.
  • Jenis tidak serasi: Pastikan ia masih melontarkan ralat apabila tiada dasar penukaran.
  • Pembaca lama: Semak sama ada perwakilan yang dikodkan semula masih memenuhi keperluannya.

Jangan keluarkan medan daripada CodingKeys semata-mata untuk mendapatkan nilai sandaran. Apple menjelaskan bahawa sifat yang digugurkan dikecualikan daripada perwakilan berkod dan dinyahkod, dan memerlukan nilai lalai untuk pematuhan penyahkodan automatik. Ini sesuai apabila sesuatu sifat sengaja dibiarkan di luar perwakilan; ini juga bermakna nilai yang dibekalkan untuk sifat itu tidak akan dibaca. Lihat panduan Apple tentang kunci pengekodan.

Letakkan peraturan keserasian berdekatan dengan logik penyahkodan medan: pilihan untuk ketiadaan yang bermakna, sandaran jelas untuk pengguguran yang diterima, dan penyahkodan biasa yang melontarkan ralat untuk nilai wajib. Lekapan dalam memori kemudian menjadikan tingkah laku yang dimaksudkan mudah disemak setiap kali model berubah.

Sumber