Written by Reinder de Vries on január 2021 2021 in App Development, iOS, Swift
Az Codable
t használhatod a Swiftben az egyéni adatformátumok, például a JSON kódolására és dekódolására natív Swift objektumokba. Hihetetlenül egyszerű a Swift objektumok leképezése JSON adatokra, és fordítva, egyszerűen az Codable
protokoll elfogadásával.
Pragmatikus iOS-fejlesztőként inkább előbb, mint utóbb találkozni fogsz a JSON-nal. Minden webszolgáltatás, a Facebooktól a Foursquare-ig, a JSON formátumot használja az adatoknak az alkalmazásodba és az alkalmazásodból történő továbbítására. Hogyan tudod hatékonyan kódolni és dekódolni ezeket a JSON adatokat Swift objektumokba?
Ezzel a bemutatóval megtanulod, hogyan dolgozhatsz JSON objektumokkal Swiftben, a Codable
protokoll segítségével. Amit megtanulsz, könnyen alkalmazható más adatformátumokra is. Elmélyedünk a JSONEncoder
és JSONDecoder
kódolásában és dekódolásában, és megmutatom, hogyan közvetíthetsz a JSON és a Swift adatok között.
Készen állsz? Gyerünk.
- Kezdjük el: Kódolás és dekódolás
- A Codable protokoll magyarázata
- Munka a Codable-vel
- A JSON dekódolása Swift objektumokká a Codable-vel
- A Swift objektumok kódolása JSON-ként a Codable-vel
- Munka a beágyazott tömbökkel és a Codable-vel
- További olvasmányok
Kezdjünk bele: Kódolás és dekódolás
Milyen problémát old meg valójában a Codable
protokoll a Swiftben? Kezdjük egy példával.
Tegyük fel, hogy egy receptalkalmazást készítesz. Az alkalmazás különböző recepteket jelenít meg egy listában, beleértve a hozzávalókat, az utasításokat és az ételekre vonatkozó alapvető információkat.
Az adatokat az alkalmazáshoz egy webszolgáltatásból, illetve azok felhőalapú API-jából szerzed be. Ez az API a JSON adatformátumot használja. Minden alkalommal, amikor egy receptet kér a webszolgáltatástól, JSON-adatokat kap vissza.
Itt egy példa egy recept JSON-adataira:
{ "name": "Spaghetti Bolognese", "author": "Reinder's Cooking Corner", "url": "https://cookingcorner.com/spaghetti-bolognese", "yield": 4, "ingredients": , "instructions": "Cook spaghetti, fry beef and garlic, add tomatoes, add love, eat!"}
Nézze meg a JSON-adatok szerkezetét.
A JSON-objektumok szögletes zárójelekbe {
és }
, a tömbök pedig szögletes zárójelekbe vannak csomagolva. A tulajdonságnevek karakterláncok, idézőjelekbe
"
csomagolva. A JSON értékek lehetnek karakterláncok, számok (idézőjelek nélkül), tömbök vagy más objektumok. Az adatok egymásba is fészkelhetők, azaz tömbök tömbökben, objektumok tömbökben stb. az adatok összetett hierarchiájának létrehozásához.
A JSON egy szövegalapú adatformátum, amelyet számos webszolgáltatás használ, beleértve a Twitter, a Facebook, a Foursquare stb. API-jait. Ha olyan alkalmazásokat készít, amelyek webalapú erőforrásokat használnak, bele fog futni a JSON-ba.
A JSON formátum jobb, mint az XML, egy gyakori alternatíva, mert hatékony, könnyen elemezhető és ember által is olvasható. A JSON elfogadott formátum a webszolgáltatások, API-k és alkalmazások számára. Az egész weben, alkalmazásokban és online szolgáltatásokban használják, mert a formátum egyszerű és rugalmas.
A JSON-nak pedig van egy kiváló képessége: bármilyen adatformátumot kódolhatunk JSON-ba, és a JSON-t dekódolhatjuk vissza bármilyen adatformátumba. Ez a kódolási és dekódolási folyamat teszi a JSON-t olyan erőteljessé.
Veheti a Swift String
, Int
, Double
, URL
, Date
, Data
, Array
és Dictionary
értékeit, és kódolhatja őket JSON-ként. Ezután elküldi őket a webszolgáltatásnak, amely dekódolja az értékeket az általa értett natív formátumba. Hasonlóképpen, a webszolgáltatás JSON-kódolt adatokat küld az alkalmazásodnak, és te dekódolod az adatokat natív típusokká, például String
, Double
és Array
.
Amikor a receptalkalmazásod megkapja a JSON-t (lásd fent), akkor az dekódolható egy Swift struktúrává, mint például ez:
struct Recipe { var name: String var author: String var url: URL var yield: Int var ingredients: var instructions: String}
A Swiftben a Codable
protokollt használjuk arra, hogy egy JSON adatobjektumból egy tényleges Swift osztály vagy struktúra legyen. Ezt nevezzük dekódolásnak, mert a JSON adatot dekódoljuk egy olyan formátumba, amelyet a Swift megért. A másik irányba is működik: a Swift objektumok JSON-ként való kódolása.
A Codable
protokoll a Swiftben valójában a Decodable
és Encodable
protokollok alias változata. Mivel a kódolást és a dekódolást gyakran együtt használjuk, a Codable
protokollt használjuk, hogy mindkét protokollt egy menetben kapjuk meg.
A kódolási/dekódolási munkafolyamat központi eleme a Swift Codable
protokollja. A következőkben nézzük meg, hogyan működik!
Nem tudod megkülönböztetni a “kódolást” és a “dekódolást”? Gondolj erre a következőképpen: Adatokat alakítunk át “kódból” és “kóddá”, mint egy Enigma gép vagy egy titkos rejtjelező. A kódolás azt jelenti, hogy az adatokat kóddá alakítjuk; en-kódolás, vagy “kódon belül/belül”. A dekódolás azt jelenti, hogy kódot alakítunk át adatokká; dekódolás, vagy “kódból/kódból”.
Felvesznek iOS-fejlesztőként
Tanulj iOS 14 alkalmazásokat készíteni a Swift 5 segítségével
Iratkozz fel iOS-fejlesztői tanfolyamomra, és tanuld meg, hogyan kezdhetsz profi iOS-fejlesztőként karriert.
A kódolható protokoll magyarázata
A JSON használata a Swift 4 előtt egy kicsit PITA volt. A JSON-t magunknak kellett a JSONSerialization
segítségével szerializálni, majd a JSON minden tulajdonságát a megfelelő Swift-típusba típusba önteni.
let json = try? JSONSerialization.jsonObject(with: data, options: )if let recipe = json as? { if let yield = recipe as? Int { recipeObject.yield = yield }}
A fenti részlet csak egy yield
értéket ragad ki a JSON-ból, és Int
-be önti. Ez rendkívül bőbeszédű, és nehéz megfelelően reagálni az esetleges hibákra és típuseltérésekre. Bár működik, nem ideális.
A SwiftyJSON-hoz hasonló könyvtárak jelentősen megkönnyítik a JSON-nal való munkát, de a JSON-adatokat még mindig le kell képezni a megfelelő Swift objektumokhoz és tulajdonságokhoz.
A Swift 4 és későbbi verziókkal a Codable
protokollt használhatjuk helyette. A Swift struktúrádnak vagy osztályodnak csupán el kell fogadnia az Codable
protokollt, és máris megkapod a JSON kódolást és dekódolást, ingyen.
A Codable
protokoll két protokoll, a Decodable és az Encodable kompozíciója. Mindkét protokoll elég minimális; csak a init(from: Decoder)
és encode(to: Encoder)
függvényeket definiálják. Más szóval ezek a függvények azt jelentik, hogy ahhoz, hogy egy típus “dekódolható” vagy “kódolható” legyen, “dekódolni kell valamiből” és “kódolni kell valamibe”.
A Codable
igazi varázsa a Decoder
és Encoder
protokollokkal történik. Ezeket a protokollokat a különböző adatformátumokat, például a JSON-t kódoló/dekódoló komponensek fogadják el. A Swiftben van néhány -kódoló:
-
PropertyListEncoder
ésPropertyListDecoder
a .plist tulajdonságlistákhoz -
JSONEncoder
ésJSONDecoder
a JSON-hoz – ezek vagyunk mi! - Az NSKeyedArchiver a
Codable
,PropertyListEncoder
,Data
ésNSCoding
A JSONDecoder
és JSONEncoder
osztályok a Decoder
és Encoder
protokollokat használják a JSON dekódolásához/dekódolásához. Ez azt is jelenti, hogy a Codable
számára írhatsz saját egyedi kódolót/dekódolót, feltéve, hogy elfogadod a Decoder
és Encoder
protokollokat!
Kell egy kis felfrissítés a Swift protokollokról? Olvassa el a Protocols In Swift Explained című részt, hogy felzárkózzon.
Working with Codable
Nézzünk egy példát. Néhány JSON adatot fogunk leképezni egy Swift struktúrára. Lényegében dekódoljuk a JSON-t egy tényleges Swift objektummá.
Először létrehozunk egy User
nevű struktúrát. Így:
struct User: Codable { var first_name: String var last_name: String var country: String}
A User
strukt három egyszerű, String
típusú tulajdonsággal rendelkezik, és megfelel az Codable
protokollnak.
Ezt követően írjunk egy kis JSON-t. Ezzel a JSON-nal fogunk dolgozni:
{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}
A JSON-adatok jellemzően egy webservice-kérés válaszaként vagy egy .json
fájlon keresztül kerülnek az alkalmazásunkba, de ebben a példában a JSON-t egyszerűen egy String
-ba tesszük. Így:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Megjegyzés: A fenti kód a többsoros karakterláncok létrehozásához a """
hármas idézőjelet használja. Ügyes!
A következő lépésünk az, hogy dekódoljuk a JSON-t, és egy User
objektummá alakítjuk. Így:
let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe
Íme, mi történik:
- Először is, a
jsonString
-et egyData
objektummá alakítjuk adata(using:)
függvény meghívásával a karakterláncon. Ez egy szükséges köztes lépés. - Ezután létrehozol egy
JSONDecoder
objektumot, és azonnal meghívod rajta adecode(_:from:)
függvényt. Ez a JSON dekódolásával ajsonData
-t egyUser
típusú objektummá alakítja. AUser.self
típust paraméterként adjuk meg. - Végül a
print(user.last_name)
segítségével kiírjuk a felhasználó vezetéknevét. Ennek azuser
értéknekUser
a típusa, tehát ez egy valódi Swift objektum!
Egyszerű, igaz? Lényegében “leképezted” a JSON objektumot egy Swift struktúrára, és dekódoltad a JSON formátumot egy natív objektummá, amivel a Swift tud dolgozni.
A fenti kódban figyelmen kívül hagyjuk a decode(_:from:)
által try!
-vel dobott hibákat. A saját kódodban a hibákat egy do-try-catch
blokkal kell kezelned. A dobható hiba például az érvénytelen JSON megadásából adódik.
A User.self
egy metatípus, egy hivatkozás magára a User
típusra. Megmondjuk a dekódolónak, hogy az adatokat a User
struct-tal akarjuk dekódolni, azzal, hogy megadjuk neki a típusra való hivatkozást.
A Codable
típus definiálásakor gyakori hiba, mint például a User
struct, a kulcsok és tulajdonságok össze nem illesztése. Ha olyan tulajdonságot adtál hozzá a struktúrádhoz, amely nincs jelen a JSON-ban, vagy más típusú, akkor hibát kapsz a JSON dekódolásakor. Az ellenkezője, azaz egy olyan tulajdonság, amely nem létezik a struktúrádban, de a JSON-ban igen, nem jelent problémát. A legegyszerűbb ezt a hibakeresést tulajdonságonként elvégezni, azaz folyamatosan hozzáadni tulajdonságokat, amíg a JSON már nem dekódolja hiba nélkül. A CodingKeys
enummal (lásd alább) azt is meghatározhatjuk, hogy milyen tulajdonságokat/kulcsokat vegyünk fel vagy hagyjunk figyelmen kívül.
JSON dekódolása Swift objektumokká a Codable-vel
Nézzük meg még egyszer az előző fejezetből vett példát, és bővítsük ki. Itt van a JSON, amivel dolgozunk:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Aztán ezt átalakítjuk egy Data
objektummá. Így:
let jsonData = jsonString.data(using: .utf8)!
Ez egy szükséges köztes lépés. Ahelyett, hogy a JSON-t sztringként ábrázolnánk, most a JSON-t natív Data
objektumként tároljuk. A karakterkódolás, amit ehhez a karakterlánchoz használunk, az UTF8.
Ha jobban megnézzük, láthatjuk, hogy a fenti kód force unwrappinget használ, hogy a data(using:)
opcionális visszatérési értékével dolgozzon. Csomagoljuk ki ezt az opcionális értéket elegánsabban!
if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }
A fenti kóddal tudunk reagálni a hibákra, amikor a data(using:)
nil
értéket ad vissza. Megjeleníthetünk például egy hibaüzenetet, vagy némán hagyhatjuk, hogy a feladat meghiúsuljon, és elmenthetjük a diagnosztikai információkat egy naplóba.
A következőkben létrehozunk egy új JSONDecoder
objektumot. Így:
let decoder = JSONDecoder()
Ezt a dekódolót használjuk ezután a JSON adatok dekódolására. Így:
let user = try! decoder.decode(User.self, from: jsonData)
Az decode(_:from:)
függvény azonban hibát dobhat. A fenti kód összeomlik, amikor ez megtörténik. Ismétlem, reagálni akarunk az esetlegesen bekövetkező hibákra. Így:
do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}
És az egész kódblokk most a következőképpen néz ki. Látja, hogy ez mennyire más?
if let jsonData = jsonString.data(using: .utf8){ let decoder = JSONDecoder() do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name) } catch { print(error.localizedDescription) }}
Soha ne hallgassuk el a hibákat. Kapjuk el a hibát, és reagáljunk rá, akár UI/UX-szal, a feladat megismétlésével, akár naplózással, összeomlással és javítással.
Munka a CodingKeys-szel
Mi van, ha a JSON tulajdonságaink, például first_name
és/vagy firstName
, eltérnek a Swift struct tulajdonságaitól? Itt jön a képbe a CodingKeys
.
Minden osztály vagy struktúra, amely megfelel a Codable
-nak, deklarálhat egy speciális, CodingKeys
nevű beágyazott felsorolást. Ezt használjuk a kódolandó és dekódolandó tulajdonságok definiálására, beleértve a nevüket is.
Nézzünk egy példát. Az alábbi User
struktúrában a tulajdonságneveket snake_case-ről camelCase-re változtattuk. Például a first_name
kulcsot mostantól firstName
-nek hívjuk.
struct User:Codable { var firstName: String var lastName: String var country: String enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" case country }}
Ha a fenti struktúrát az előző szakaszok példáival együtt használjuk, látni fogjuk, hogy egy User
objektumot használhatunk az új tulajdonságnevekkel. Így:
print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom
A CodingKeys
felsorolás az eseteit tulajdonságokra képezi le, és a karakterláncértékek segítségével kiválasztja a megfelelő tulajdonságneveket a JSON-adatokban. Az enumban szereplő eset a struktúrában használni kívánt tulajdonság neve, az értéke pedig a kulcs neve a JSON-adatokban.
Swift objektumok kódolása JSON-ként a Codable-vel
Eleddig a JSON-adatok Swift objektumokká történő dekódolására koncentráltunk. Mi a helyzet a másik irányban? Az objektumokat is kódolhatjuk JSON-ként? Igen, miért ne!
var user = User()user.firstName = "Bob"user.lastName = "and Alice"user.country = "Cryptoland"let jsonData = try! JSONEncoder().encode(user)let jsonString = String(data: jsonData, encoding: .utf8)!print(jsonString)
A kimenet pedig:
{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}
Mi történik itt?
- Először létrehozunk egy
User
objektumot, és hozzárendelünk néhány értéket a tulajdonságaihoz - Majd a
encode(_:)
segítségével kódoljuk auser
objektumot egy JSONData
objektumba - Végül, átalakítod az
Data
objektumotString
-ba és kinyomtatod
Ha jobban megnézed, láthatod, hogy a kódolás ugyanazokat a lépéseket követi, mint a dekódolás, csak fordítva. A Swift objektumtól a dekóderen keresztül haladva egy JSON sztringet eredményez.
Mint korábban, kibővíthetjük a példát a hibák kezelésére? Igen! Így:
let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinteddo { let jsonData = try encoder.encode(user) if let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) }} catch { print(error.localizedDescription)}
A fenti példában a kódoló outputFormatting
tulajdonságát is használjuk a JSON adatok “szépen kiírására”. Ez szóközöket, tabulátorokat és újsorokat ad hozzá, hogy a JSON karakterlánc könnyebben olvasható legyen. Így:
{ "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}
Félelmetes!
Azért érdemes megjegyezni, hogy a Swiftben és a JSON-ban a szótáraknak nincs rögzített rendezési sorrendje. Ezért a JSON-ban a country
, first_name
stb. kulcsoknál eltérő rendezési sorrendet fogsz látni, ha a fenti kódot néhányszor lefuttatod. A legtöbb JSON-t kimenő webszolgáltatás egy sémán keresztül kikényszerít egy fix rendezési sorrendet, ami határozottan megkönnyíti a JSON olvasását és navigálását.
Munka beágyazott tömbökkel és Codable
Eleddig egyszerű JSON objektumokkal dolgoztunk – csak egy osztály néhány tulajdonsággal. De mi van akkor, ha az adatok, amelyekkel dolgozunk, összetettebbek?
A JSON-adatok, amelyeket például egy Twitter vagy Facebook webszolgáltatásból kapunk vissza, gyakran egymásba ágyazottak. Ez azt jelenti, hogy a kívánt adatok 2-3 szintű JSON tömbök és szótárak között vannak eltemetve. Most mi legyen?
Lássunk először egy egyszerű példát. Itt van a Swift struktúra, amivel dolgozni fogunk:
struct User: Codable { var first_name: String var last_name: String}
Ezután itt vannak a JSON adatok, amiket dekódolni szeretnénk:
let jsonString = """"""
Ha jobban megnézzük, láthatjuk, hogy a nekünk szükséges adatok tömbként vannak tárolva. A JSON nem egy egyszerű objektum, hanem egy 3 User
objektumot tartalmazó tömb. Az (tömb) és a
{ }
(objektum) szögletes zárójelekből tudjuk, hogy ez egy objektumokból álló tömb. A több elemet mindig vesszővel választjuk el.
Hogyan tudjuk dekódolni ezeket a User
objektumokat? Íme, hogyan:
let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}
A fenti kódrészlet rendkívül hasonló az eddig látottakhoz, de van egy fontos különbség. Látod, milyen típust adunk meg a decode(_:from:)
függvénynek? A típus (.self-el), vagyis “felhasználói objektumok tömbje”. Ahelyett, hogy egyetlen
User
objektummal dolgoznánk, egy csomót akarunk dekódolni, és ezt a tömbtípussal jelöljük.
A következőkben arról fogunk beszélni, hogyan dolgozhatunk összetettebb, egymásba ágyazott típusokkal. Vegyük például, hogy a User
objektumok tömbje egy JSON szótárba van beágyazva. Így:
let jsonString = """{ "users": }"""
A fenti JSON részletben a legfelső szintű elem egy szótár (vagy “objektum”). Csak egy kulcs-érték párja van: egy users
tömb. A kívánt adatok ebben a tömbben vannak. Hogyan jutunk hozzá?
Fontos, hogy a megközelítésünk ne legyen túl bonyolult. Könnyű azt gondolni, hogy valami fejlett JSON-elemzésre lesz szükségünk, de mint kiderült, valójában Swift-struktúrákat is be tudunk fészkelni. A teljes JSON-t egy struktúraként fogjuk leírni, benne a User
struktúrával.
Nézzük meg ezt:
struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }
Íme, mi történik:
- A legfelső szintű JSON-szótár a
Response
típusnak felel meg. Csakúgy, mint korábban, ez a típus megfelel aCodable
-nak, hogy támogassa a JSON kódolást és dekódolást. - A struktúrán belül definiáltunk egy másik struktúrát, amelynek a neve
User
. Ez pontosan ugyanaz a típus, amit már korábban is használtunk. Ez a struktúra “beágyazott”. - A
Response
struktúrának van egyusers
nevű,típusú tulajdonsága, vagyis felhasználói objektumok tömbje.
A király dolog az, hogy szemantikailag mind a JSON, mind a Swift adatstruktúra pontosan ugyanaz. Csak más szintaxist használnak. Egy beágyazott tömböt definiáltunk a legfelső szintű szótáron belül, ahogyan a Response
struktúrának is van egy beágyazott User
struktúrája és egy users
tulajdonsága benne.
Az, hogy ez működjön, már gyerekjáték. Ezt nézd meg:
let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}
Látszik, hogy a Response
típust használjuk a JSON dekódolásához, majd a response.users
tulajdonságon loopolunk? Ellenőrizd ezt a Response
struktúrával, és a JSON-nal. Kiválasztjuk a users
kulcs-érték párt a legfelső szintű szótárból, és a benne lévő objektumokat User
objektumokra képezzük le. Ügyes!
A beágyazott típusok használata nagyszerű általános célú megközelítés az összetett JSON adatstruktúrákhoz. Ha olyan JSON-darabkára bukkansz, amelyet nem tudsz könnyen ábrázolni egy kézzelfogható típusban, mint például User
vagy Tweet
, próbáld meg kibővíteni a típust valami olyasmire, mint Response
vagy UserCollection
. Ahelyett, hogy mélyebbre mennél, menj szélesebbre, és építsd be a teljes JSON-t. Használjon egymásba ágyazott típusokat, hogy eljusson a kívánt adatokhoz. Itt is érdemes megjegyezni, hogy nem kell a User
struktúrát a Response
struktúrába illesztened – kívül is mehet rajta.
Fogadj fel iOS-fejlesztőként
Tanulj meg iOS 14 alkalmazásokat készíteni a Swift 5 segítségével
Iratkozz fel iOS-fejlesztői tanfolyamomra, és tanuld meg, hogyan kezdhetsz profi iOS-fejlesztőként karriert.
További olvasnivalók
És ez minden! Most már tudod:
- Hogyan használd a
Codable
-t kódolható és dekódolható objektumok létrehozásához - Hogyan használd a
JSONDecoder
ésJSONEncoder
-t JSON objektumok és Swift megfelelőik kódolásához és dekódolásához - Mire való a kódolás és dekódolás, és miért fontos a mindennapi iOS-fejlesztésben
- Hogyan dolgozzunk összetettebb JSON-adatokkal, és hogyan használjuk az egymásba ágyazott típusokat
Még többet szeretne megtudni? Nézze meg ezeket a forrásokat:
- Tömbök, szótárak és struktúrák
- Munka JSON-nal a Swiftben a SwiftyJSON-nal
- A tárgyközpontú programozás bevezetése a Swiftben
- How To: Hogyan keressünk egy elemet egy tömbben Swiftben
- How To Use Apple’s Developer Documentation For Fun And Profit
- How To Find Strings With Regular Expressions In Swift
- Why App Architecture Matters
- Strings in Swift Explained
- Working with Files on iOS with Swift
- Storing Data with NSCoding and NSKeyedArchiver