Özyinelemeli bir Swift enum’uyla iç içe düz değerleri korumak için her değer türü için özel kodlama ve çözme uygulayın. Bu anlatımda, enum case’leri için sarmalayıcılar eklemeden, tamamen bellek içinde Foundation nesnelerine dönüştürülüp geri alınabilen sentetik bir Mac uygulama modeli oluşturuluyor.
Sentezlenen Codable gösterimi neden değiştirir?
Bir Mac masaüstü uygulamasında etiketler, sayısal değerler, anahtarlar, listeler ve iç içe gruplar içeren küçük bir yerleşim önizlemesi olduğunu varsayalım. Özyinelemeli bir enum bu değerleri tam olarak tanımlayabilir. Buradaki somut görev, bu enum’u düz bir Foundation nesne grafiğine dönüştürmek ve desteklenen değer türlerini koruyarak yeniden oluşturmaktır.
Otomatik uyumluluk, gereken gösterimi sağlamaz. Swift’in uygulanan enum sentez tasarımı, case adını dıştaki anahtarlı kapsayıcıya yerleştirir, ardından ilişkili değerleri başka bir kapsayıcıya koyar. Etiketsiz ilişkili değerlere _0 gibi oluşturulmuş anahtarlar atanır. Sonuç olarak bir string case’i düz bir string olmak yerine yapısal sarmalayıcılar kazanır. Bu kurallar İlişkili değerlere sahip enum’lar için Codable sentezi içinde belgelenmiştir.
CodingKeys değerlerini değiştirmek bu sarmalayıcıları yeniden adlandırabilir, ancak sentezi korumak yapısal modeli de korur. Desteklenen çözüm, init(from:) ve encode(to:) yöntemlerini kendiniz uygulamaktır. Apple, Swift yapısı ile kodlanan yapısı farklı olan türler için elle uyumluluğu Özel Türleri Kodlama ve Çözme içinde açıkça belgeliyor.
Özyineleme ve gösterim birbirinden ayrı kararlardır. Swift’in indirect değiştiricisi özyinelemeli enum depolamayı sağlar; özel kodlama yöntemleri kodlanan biçimi belirler. Dil kılavuzu, ilişkili değer case’lerine dolaylılık uygulamak için indirect ifadesinin enum bildiriminden önce yazılmasını Enum’lar içinde açıklar.
Düz gösterim tam olarak neyi korumalı?
Bu örnek için aşağıdaki sözleşmeyi kullanın. Sözleşme, sentetik bir yerleşim önizlemesine uygun küçük bir değer kümesini özellikle destekler. Foundation’ın belgelenen dönüştürme kuralları string’lere, sayılara, dizilere, sözlüklere ve null nesnelerine izin verir; sözlük anahtarlarının string olmasını gerektirir ve sonlu olmayan sayıları dışarıda bırakır. Bkz. JSONSerialization.
| Enum case’i | Düz değer | Uyumluluk gereksinimi |
|---|---|---|
null |
NSNull |
Açık null değerini koru |
bool |
Boolean değeri | Sayısal sıfır ve birden ayrı tut |
number |
Sonlu Double değeri |
Double türünün hassasiyet sınırlarını kabul et |
string |
String | Metni sayısal dönüşüm olmadan koru |
array |
Düz değerlerden oluşan dizi | Öğe sırasını ve boş dizileri koru |
object |
String anahtarlı sözlük | Anahtarları, iç içe değerleri ve boş nesneleri koru |
Buradaki uyumluluk, belirtilen sayısal sınırlar içinde bu kategorilerin ve değerlerinin korunması anlamına gelir. Belirli bir Foundation sayı alt sınıfının ya da sözlük anahtarlarının sıralanma biçiminin korunmasını gerektirmez. Açık bir null girdisi de korunmalıdır: "subtitle": NSNull() içeren bir nesnenin model anlamı, subtitle anahtarı olmayan bir nesneden farklıdır.
Bu sözleşmeyi benimsemeden önce sayıların gerçekten tek bir Double case’ini paylaşıp paylaşamayacağına karar verin. Apple, Double türünü 64 bit kayan nokta türü olarak belgeler ve sıradan dönüşümler yuvarlamaya neden olabildiğinden tam dönüşüm başlatıcıları sağlar. Modeliniz büyük tam sayıları tam olarak saklamalıysa, bunları dönüştürmeden önce açık bir sayı politikası belirleyin. Bir tam sayı case’i eklemek, düz sayısal değerler Swift enum case’lerinin kimliğini taşımadığından bir çözme kuralı da gerektirir. Bkz. Double.
Özyinelemeli enum’u nasıl uygularsınız?
Foundation’ın kullanılabildiği güncel bir Mac Swift ortamı kullanın. Sıradaki üç kod bloğu, sırasıyla tek ve eksiksiz bir üst düzey örnek oluşturur: enum, dönüştürme işlevleri ve çalıştırılabilir bir denetim yordamı. Tüm örnek değerler bellekte oluşturulur. Araştırma ortamında Swift bulunmadığından örnek incelendi, ancak çalıştırılmadı.
Çözücü önce null olup olmadığını denetler, ardından desteklenen temel türleri dener. Apple’ın SingleValueDecodingContainer API’si decodeNil() dahil bu işlemleri sağlar. Yerel yardımcı işlev, yalnızca tür uyuşmazlığını başka bir temel türü denemek için neden kabul eder; diğer çözme hatalarını üst katmana iletir.
import Foundation
indirect enum PlainValue: Codable, Equatable {
case null
case bool(Bool)
case number(Double)
case string(String)
case array([PlainValue])
case object([String: PlainValue])
init(from decoder: Decoder) throws {
let scalar = try decoder.singleValueContainer()
if scalar.decodeNil() {
self = .null
return
}
func matching<T: Decodable>(_ type: T.Type) throws -> T? {
do {
return try scalar.decode(type)
} catch DecodingError.typeMismatch(_, _) {
return nil
}
}
if let value = try matching(Bool.self) {
self = .bool(value)
return
}
if let value = try matching(String.self) {
self = .string(value)
return
}
if let value = try matching(Double.self) {
guard value.isFinite else {
throw DecodingError.dataCorrupted(.init(
codingPath: decoder.codingPath,
debugDescription: "Expected a finite number."
))
}
self = .number(value)
return
}
var sequence: UnkeyedDecodingContainer?
do {
sequence = try decoder.unkeyedContainer()
} catch DecodingError.typeMismatch(_, _) {
sequence = nil
}
if var items = sequence {
var values: [PlainValue] = []
while !items.isAtEnd {
values.append(try items.decode(PlainValue.self))
}
self = .array(values)
return
}
self = .object(try scalar.decode([String: PlainValue].self))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .bool(let value):
try container.encode(value)
case .number(let value):
guard value.isFinite else {
throw EncodingError.invalidValue(value, .init(
codingPath: encoder.codingPath,
debugDescription: "Expected a finite number."
))
}
try container.encode(value)
case .string(let value):
try container.encode(value)
case .array(let values):
try container.encode(values)
case .object(let values):
try container.encode(values)
}
}
}
Her kodlama dalı yükünü doğrudan yazar. Null dalı açıkça encodeNil() çağırır; koleksiyon dalları dizilerin ve sözlüklerin kodlama uygulamalarını kullanır. Apple hem temel hem de genel kodlama işlemlerini SingleValueEncodingContainer içinde, koleksiyon uyumluluğunu ise Özel Türleri Kodlama ve Çözme içinde belgeler.
Dizi algılama, dizi öğelerinin çözülmesinden özellikle ayrı tutulmuştur. Geçerli değer dizi değilse, anahtarsız kapsayıcı istemek tür uyuşmazlığı hatası verebilir. Bir dizi tanındıktan sonra her öğe sıradan try ile çözülür; böylece bir öğe hatası, ilgisiz bir nesne çözme girişimini tetiklemek yerine dışarı iletilir. Belgelenen davranış unkeyedContainer() ve UnkeyedDecodingContainer içinde açıklanmıştır.
Bu uygulama, Foundation’ın JSON kodlama yolunu ve belirtilen değer kümesini hedefler. Başka bir kodlayıcı veya çözücü kullanmadan önce aynı sözleşmeye göre test edin; tek başına Codable uyumluluğu, farklı gösterim biçimlerinde davranışın aynı olduğunu kanıtlamaz.
Tamamen bellek içinde Foundation nesnelerine nasıl dönüştürürsünüz?
Foundation iki kullanışlı sınır sunar: JSONEncoder, kodlanabilir bir değerden Data üretir; JSONDecoder ise Data üzerinden çözülebilir bir değeri yeniden oluşturur. JSONSerialization, bu ara gösterim ile Foundation nesneleri arasında dönüşüm yapar. Belgelenen bu işlemleri birleştirmek aşağıdaki bellek içi köprüyü sağlar. Bkz. JSONEncoder, JSONDecoder ve JSONSerialization.
func foundationObject(from value: PlainValue) throws -> Any {
let bytes = try JSONEncoder().encode(value)
return try JSONSerialization.jsonObject(
with: bytes,
options: [.fragmentsAllowed]
)
}
func plainValue(from object: Any) throws -> PlainValue {
let bytes = try JSONSerialization.data(
withJSONObject: object,
options: [.fragmentsAllowed]
)
return try JSONDecoder().decode(PlainValue.self, from: bytes)
}
Ara Data bellekte kalır. Bu işlevler serileştirme yapar; enum ile Foundation koleksiyonları arasında sıfır kopyalı tür dönüşümleri değildir. Dönüşümü, masaüstü özelliğinizin düz nesne grafiğine gerçekten ihtiyaç duyduğu sınıra yerleştirin; türlenmiş model işlemlerinde enum’u kullanmayı sürdürün.
Her iki .fragmentsAllowed seçeneği de önemlidir, çünkü özyinelemeli bir değerin kökü skaler olabilir. En üst düzey string, Boolean, sayı veya null da iç içe bir değerle aynı işleme ihtiyaç duyar. Foundation bu davranışı ReadingOptions.fragmentsAllowed ve WritingOptions.fragmentsAllowed için ayrı ayrı belgeler.
Geri dönüşüm işlevi, Foundation API’sinin girdi türü bu olduğundan Any kabul eder; ancak desteklenen değer alanı yukarıdaki sözleşmeyle sınırlı kalır. Dönüştürme hataları çağırana iletilir. Başarısız bir dönüşümü boş bir nesneyle değiştirmeyin veya dönüştürülemeyen öğeleri kaldırmayın; bu, modeli sessizce değiştirir.
Hangi denetimler uyumluluğu gösterir?
Tek başına gidiş dönüş denetimi ilk sorunu gözden kaçırabilir: sentezlenen enum kodlaması, kendi sarmalanmış yapısını başarıyla gidip dönebilir. Hem enum eşitliğini hem de düz biçimi denetleyin. Aşağıdaki yordam bağımsız bir Foundation grafiği oluşturur, beklenen enum modelini doğrular, çıktının gerçek kök anahtarlarını denetler ve her kök kategorisini sınar.
func runExample() throws {
let plain: [String: Any] = [
"title": "Tile preview",
"enabled": true,
"spacing": 8.5,
"subtitle": NSNull(),
"tiles": [
["label": "Circle", "size": 24.0],
["label": "Square", "size": 32.0]
]
]
let expected: PlainValue = .object([
"title": .string("Tile preview"),
"enabled": .bool(true),
"spacing": .number(8.5),
"subtitle": .null,
"tiles": .array([
.object([
"label": .string("Circle"),
"size": .number(24)
]),
.object([
"label": .string("Square"),
"size": .number(32)
])
])
])
let model = try plainValue(from: plain)
precondition(model == expected)
let output = try foundationObject(from: model)
guard let object = output as? [String: Any] else {
preconditionFailure("Expected a plain object root.")
}
precondition(Set(object.keys) == Set(plain.keys))
precondition(object["title"] as? String == "Tile preview")
precondition(object["subtitle"] is NSNull)
precondition((object["tiles"] as? [Any])?.count == 2)
let restored = try plainValue(from: output)
precondition(restored == expected)
let roots: [PlainValue] = [
.null, .bool(false), .bool(true),
.number(0), .number(1), .number(8.5),
.string("1"), .array([]), .object([:]),
expected
]
for root in roots {
let graph = try foundationObject(from: root)
let decoded = try plainValue(from: graph)
precondition(decoded == root)
}
do {
_ = try foundationObject(from: .number(.infinity))
preconditionFailure("Expected nonfinite number rejection.")
} catch EncodingError.invalidValue(_, _) {
// Expected: this model requires finite numbers.
}
print("All compatibility checks passed.")
}
try runExample()
Beklenen başarı iletisi All compatibility checks passed. şeklindedir. Kök anahtar denetimi, eklenmiş bir dış case sarmalayıcısını yakalar. Bağımsız beklenen model, Boolean ile sayı ve sayıya benzeyen metin ile sayı arasındaki farklar dahil olmak üzere yanlış case seçimini yakalar. Null denetimi subtitle anahtarının mevcut kaldığını doğrular; kök denetimleri ise skaler parçaları kapsar ve boş dizileri boş nesnelerden ayırır.
Mevcut bir masaüstü modeli için bu tanılama sırasını sentetik değerlerine uyarlayın: önce beklenen enum’u doğrulayın, ardından düz biçimi inceleyin, sonra enum’u yeniden oluşturun ve son olarak sınır durumlarını çalıştırın. Başarısız bir denetimin hangi dönüşümün değeri değiştirdiğini göstermesi için ara sonuçlara ad verin.
Bu yaklaşımın sınırları nelerdir?
Başlıca sınırlama sayısal sözleşmedir. Tek bir Double case’i, her sayısal Foundation girdisinin tam olarak korunmasını garanti edemez. Tam tam sayı dönüşümünün önemli olduğu durumlarda, bu modele girmeden önce temsil edilebilirliği doğrulayın veya sayı case’lerini ve bunların çözme kurallarını yeniden tasarlayın. Serileştirmenin başarılı olması, sayısal değerin tam korunduğunu göstermez. Apple’ın Double belgeleri, yuvarlanan dönüşümlerle tam dönüşümleri birbirinden ayırır.
Örnek, kodlamanın ve çözmenin her iki yönünde de sonlu olmayan sayıları açıkça reddeder. Böylece enum’un sınırı, düz Foundation gösteriminin belgelenmiş kısıtlarıyla uyumlu kalır. Özyinelemeli yapı, sınırsız iç içe geçme kapasitesi anlamına gelmez; bu nedenle sentetik model ağaçlarını makul boyutta tutun ve dönüştürme hatalarının çağıran özelliğe ulaşmasını sağlayın.
Yerleşim önizlemesi görevi için tamamlanmış sonuç, özel kodlaması düz string’leri, Boolean değerlerini, sonlu sayıları, null değerlerini, dizileri ve string anahtarlı nesneleri koruyan türlenmiş bir özyinelemeli enum’dur. Case’leri veya dönüştürme mantığını her değiştirdiğinizde biçim denetimlerini gidiş dönüş denetimleriyle birlikte kullanın; bu iki denetim birlikte amaçlanan gösterimi somut ve gözden geçirilebilir kılar.
Kaynaklar
- İlişkili değerlere sahip enum’lar için Codable sentezi — Swift Project.
- Enum’lar — Swift Project.
- Özel Türleri Kodlama ve Çözme — Apple.
- SingleValueDecodingContainer — Apple.
- SingleValueEncodingContainer — Apple.
- unkeyedContainer() — Apple.
- UnkeyedDecodingContainer — Apple.
- JSONEncoder — Apple.
- JSONDecoder — Apple.
- JSONSerialization — Apple.
- ReadingOptions.fragmentsAllowed — Apple.
- WritingOptions.fragmentsAllowed — Apple.
- Double — Apple.