Mimi hivi karibuni alikutana na changamoto ya kuvutia inayohusiana na decoding JSON katika Swift. Kama watengenezaji wengi, wakati wa kukabiliana na jibu kubwa, ngumu JSON, hisia yangu ya kwanza ilikuwa kufikia kwa zana za "kurekebisha haraka". , mabadiliko mbalimbali ya JSON-to-Swift, na hata mifano ya kisasa ya AI - itakabiliana na muundo wa data mbaya, wa kurudia. Quicktype Kwa ukweli, nilikuwa nimefunga kabisa. Swali/ Utangulizi wa JSON Tatizo linakuja wakati unakutana na API ya zamani au majibu yenye muundo mbaya ambayo inatumia mali zilizo na nambari ya "flat" badala ya mfululizo safi. Angalia sampuli hii ya JSON: { "meals": [ { "idMeal": "52771", "strMeal": "Spicy Arrabiata Penne", "strInstructions": "Bring a large pot of water to a boil...", "strMealThumb": "https://www.themealdb.com/images/media/meals/ustsqw1468250014.jpg", "strIngredient1": "penne rigate", "strIngredient2": "olive oil", "strIngredient3": "garlic", "strIngredient4": "chopped tomatoes", "strIngredient5": "red chilli flakes", // ... this continues up to strIngredient20 "strMeasure1": "1 pound", "strMeasure2": "1/4 cup", "strMeasure3": "3 cloves", // ... this continues up to strMeasure20 } ] } Kwa nini mabadiliko ya mtandaoni inashindwa Nilipounganisha hii kwenye zana za uongofu wa kawaida, matokeo yalikuwa mabaya ya utunzaji. Wameunda "mduara wa mali" ambayo ilionekana kama hii: struct Meal: Codable { let idMeal: String let strMeal: String let strInstructions: String? let strMealThumb: String? // The repetitive property nightmare let strIngredient1: String? let strIngredient2: String? let strIngredient3: String? // ... let strIngredient20: String? let strMeasure1: String? let strMeasure2: String? let strMeasure3: String? // ... let strMeasure20: String? } Hebu tuwe waaminifu, msimbo uliotengenezwa na zana hizi za mtandaoni ni sehemu ya "mbuzi" kwa mradi wowote mkubwa. Si tu ni usio na kiwango, lakini kufikiria kuangalia juu ya uso wa developer yako ya juu wakati wa tathmini ya PR wakati wanaona mali zaidi ya 40 zinazohitajika. Ni ndoto ya matengenezo na shida kwa sifa yako ya kitaaluma. Niliamua kuchukua udhibiti wa mchakato wa decoding ili iwe safi, Swifty, na - muhimu zaidi - Hapa ni jinsi nilianzisha suluhisho na kwa nini inafanya kazi. production-ready Silaha ya siri: Kwa nini tunatumia muundo wa codingKeys Katika 99% ya tutorials ya Swift, unaweza kuona Kufafanuliwa kama Enums ni nzuri wakati unajua kila kifungo kimoja wakati wa kuunganisha. Lakini katika kesi yetu, tuna JSON "flat" na kifungo kama vile ya juu ya Kuandika enum na kesi 40 sio tu kuchoka - ni uhandisi mbaya. Badala yake CodingKeys enum strIngredient1 strIngredient2 20 struct 1.Ukiukwaji wa mahitaji ya Mkataba Kwa mujibu wa Mtu anapaswa kukabiliana na wote wawili na ya Kwa kutumia vifaa vingine, tunaweza kuingia kwenye initializer wakati wa runtime. CodingKey String Int yoyote ya struct CodingKeys: CodingKey { let stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } // This allows us to map any raw string from the JSON to our logic init(rawValue: String) { self.stringValue = rawValue } init?(intValue: Int) { return nil } // We don't need integer keys here } Mpangilio wa "Ugly" Key kwa majina safi Huna haja ya kushikamana na makubaliano ya jina la API ndani ya programu yako. Kumbuka jinsi nilivyotumia Hii inahifadhi logic nyingine ya decoding inaweza kusomwa wakati wa kuweka "kutoka" API keys kujitenga ndani ya muundo huu. static var static var name = CodingKeys(rawValue: "strMeal") static var thumb = CodingKeys(rawValue: "strMealThumb") static var instructions = CodingKeys(rawValue: "strInstructions") Nguvu ya Generation Key Dynamic Hii ni sehemu ambayo inafanya mbinu hii ya juu kuliko msimbo wowote unaozalishwa na AI. Tumeunda kazi za static ambazo zinatumia ili kuzalisha vifungo kwenye ndege. string interpolation static func strIngredient(_ index: Int) -> Self { CodingKeys(rawValue: "strIngredient\(index)") } static func strMeasure(_ index: Int) -> Self { CodingKeys(rawValue: "strMeasure\(index)") } Badala ya kusafisha ya Sasa tunajua bora na si kwa ajili ya vizazi vipya ni wakati ambapo hakuna simu za mkononi kwamba pengine ilikuwa ya kutisha Mtindo wa zamani wa kipindi ambapo hawapendi kuwa [...] katika initializer yetu, sisi tu kuita kazi hizi. ni safi, inaweza kutumika tena, na ni vigumu sana kufanya typpo kuliko kuandika kesi 40 za kibinafsi. strIngredient1 strIngredient2 1...20 Kujenga mfano ambao kwa kweli una maana JSON ya awali inazingatia kiungo na kupima yake kama wageni wawili wanaoishi katika nyumba tofauti. Katika programu yetu, kuna wanandoa. Kwa kuunganisha kiungo cha kujitolea, tunabadilisha usanifu wa data kwenye chanzo: struct Ingredient: Decodable, Hashable { let id: Int let name: String let measure: String } Kwa nini na ya ? Hashable Id ya Hifadhi ya Id ya Nimeongeza moja ya kwa kutumia kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha kiwango cha na ya inahitaji data ya kibinafsi.Kwa kuzingatia Tunahakikisha kuwa: id List ForEach Hashable Hakuna UI Glitches: SwiftUI haitachanganya ikiwa viungo viwili tofauti vina jina moja (kama aina mbili tofauti za "Salt"). Ufanisi: Vyanzo vya data vya kutosha vinapenda vitu vya hashable. Jinsi ya kuondoa "harufu ya API" Kabla ya kufikia initializer, angalia jinsi tunavyofafanua mali zetu kuu. Sisi si tu nakala kile API inatuza; sisi kutafsiri kwa . Clean Swift let name: String let thumb: URL? let instructions: String let ingredients: [Ingredient] Goodbye str Prefix: Sisi kupoteza notation Hungarian. jina ni bora kuliko strMeal. Aina sahihi: Tunafafanua miniature moja kwa moja kwenye URL?. Ikiwa API inatuma kiungo kilichopungua au mstari tupu, decoder yetu hutafakari kwa heshima wakati wa hatua ya uchambuzi, sio baadaye katika View. The Smart Initializer: yetu “Data Bouncer” Badala ya kukubali kwa makini kila kifungo ambacho JSON inatoa, inafanya kazi kama bouncer katika klabu - tu data halali inakuja. init(from:) init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 1. Decode simple properties using our clean aliases self.name = try container.decode(String.self, forKey: .name) self.thumb = try? container.decode(URL.self, forKey: .thumb) self.instructions = try container.decode(String.self, forKey: .instructions) // 2. The Dynamic Decoding Loop var ingredients: [Ingredient] = [] for index in 1...20 { // We use 'try?' because some keys might be null or missing if let name = try? container.decode(String.self, forKey: .strIngredient(index)), let measure = try? container.decode(String.self, forKey: .strMeasure(index)), !name.isEmpty, !measure.isEmpty { // We only save it if the name AND measure are valid and non-empty ingredients.append(Ingredient(id: index, name: name, measure: measure)) } } self.ingredients = ingredients } Matokeo ya mwisho: Clean, Swifty, na UI tayari Baada ya yote kwamba kazi nyuma ya kushangaza, angalia kile tumefanikiwa. Tumefanya mabaya ya "flat" JSON kuwa mfano ambao ni furaha ya kutumia. Hapa ni nini wengine wa programu yako wanaona sasa: struct MealDetail { let name: String let instructions: String let thumb: URL? let ingredients: [Ingredient] } Ufafanuzi wa kipekee katika UI Kwa sababu tumefanya uzito mkubwa wakati wa awamu ya ufunuo - kufuta thamani za tupu na makundi ya viungo - SwiftUI yetu inakuwa safi sana. Hatuna haja ya mantiki ngumu yoyote katika View; tu tunapiga data moja kwa moja kwenye vipengele. The Cherry juu ya juu: kufanya Mocking rahisi Unaweza kuwa umeona athari ndogo: wakati sisi kufafanua tabia , Swift huacha kuzalisha initializer ya default ya mwanachama. Hii inaweza kufanya maandishi ya majaribio ya kitabu au Previews ya SwiftUI kuwa mbaya kidogo. init(from: Decoder) Ili kurekebisha hili na kuweka msingi wetu wa msimbo "kujaribu-friendly," tunaweza kuongeza upanuzi huu rahisi. Hii inakuwezesha kuunda data "Mock" kwa UI yetu bila haja ya faili ya JSON. extension MealDetail { // Restoring the ability to create manual instances for Mocks and Tests init(name: String, thumb: URL?, instructions: String, ingredients: [Ingredient]) { self.name = name self.thumb = thumb self.instructions = instructions self.ingredients = ingredients } } Sasa, kuunda preview ni rahisi kama vile: let mock = MealDetail(name: "Pasta", thumb: nil, instructions: "Cook it.", ingredients: []) Mwisho wa Wakati ujao unakabiliwa na API mbaya, kumbuka: Zana za mtandaoni na AI zinaweza kukupa suluhisho la haraka la "kopi-paste", lakini mara nyingi husababisha madeni ya kiufundi. Kwa kufanya hivyo, unahitaji kuunda code ambayo ni: don’t let the backend dictate your frontend architecture. Decodable Kueleweka: Majina ya mali ya wazi, ya msingi ya nia. Robust: Filters nje ya data tupu au uharibifu katika chanzo. Kuhifadhiwa: Rahisi kujaribu na rahisi kuonyeshwa katika UI. Furaha coding, na kuweka mifano yako safi! Msimbo kamili ni hapa: https://github.com/PavelAndreev13/NetworkLayer