Scris de Reinder de Vries pe 20 ianuarie 2021 în Dezvoltare aplicații, iOS, Swift
Puteți utiliza Codable
în Swift pentru a codifica și decodifica formate de date personalizate, cum ar fi JSON, în obiecte native Swift. Este incredibil de ușor să mapezi obiecte Swift în date JSON și viceversa, prin simpla adoptare a protocolului Codable
.
Ca dezvoltator iOS pragmatic, veți întâlni JSON mai devreme sau mai târziu. Fiecare serviciu web, de la Facebook la Foursquare, utilizează formatul JSON pentru a introduce și a scoate date din aplicația dvs. Cum puteți codifica și decodifica eficient acele date JSON în obiecte Swift?
În acest tutorial veți învăța cum să lucrați cu obiecte JSON în Swift, folosind protocolul Codable
. Ceea ce veți învăța, poate fi aplicat cu ușurință și la alte formate de date. Vom intra în codificare și decodificare cu JSONEncoder
și JSONDecoder
, și vă voi arăta cum să mediați între datele JSON și Swift.
Pregătiți? Să mergem.
- Începeți: Codificare și decodificare
- Explicarea protocolului Codable
- Lucrul cu Codable
- Decodarea JSON în obiecte Swift cu Codable
- Codificarea obiectelor Swift ca JSON cu Codable
- Lucrul cu array-uri imbricate și Codable
- Lecturi suplimentare
Începeți: Codificare și decodificare
Ce problemă rezolvă de fapt protocolul Codable
din Swift? Să începem cu un exemplu.
Imaginați-vă că construiți o aplicație de rețete. Aplicația afișează diverse rețete într-o listă, inclusiv ingredientele, instrucțiunile și informațiile de bază despre alimente.
Obțineți datele pentru aplicație de la un serviciu web și API-ul lor bazat pe cloud. Această API utilizează formatul de date JSON. De fiecare dată când solicitați o rețetă de la serviciul web, primiți înapoi date JSON.
Iată un exemplu de date JSON pentru o rețetă:
{ "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!"}
Aruncați o privire la structura datelor JSON.
Obiectele JSON sunt înfășurate în paranteze zbârcite {
și }
, iar array-urile sunt înfășurate în paranteze pătrate . Numele proprietăților sunt șiruri de caractere, înfășurate între ghilimele
"
. Valorile în JSON pot fi șiruri de caractere, numere (fără ghilimele), matrici sau alte obiecte. De asemenea, puteți anina date, adică array-uri în array-uri, obiecte în array-uri etc., pentru a crea o ierarhie complexă de date.
JSON este un format de date bazat pe text pe care îl folosesc multe servicii web, inclusiv API-urile de la Twitter, Facebook, Foursquare și așa mai departe. Dacă construiți aplicații care utilizează resurse bazate pe web, vă veți întâlni cu JSON.
Foratul JSON este superior lui XML, o alternativă comună, deoarece este eficient, ușor de analizat și lizibil de către oameni. JSON este un format agreat pentru servicii web, API-uri și aplicații. Este utilizat pe tot parcursul web-ului, aplicațiilor și serviciilor online, deoarece formatul este simplu și flexibil.
Și JSON are o capacitate superbă: puteți codifica orice format de date în JSON și decodifica JSON înapoi în orice format de date. Acest proces de codificare și decodificare este ceea ce face ca JSON să fie atât de puternic.
Puteți lua valorile Swift String
, Int
, Double
, URL
, Date
, Data
, Array
și Dictionary
și să le codificați ca JSON. Apoi le trimiteți către serviciul web, care decodifică valorile într-un format nativ pe care îl înțelege. În mod similar, serviciul web trimite aplicației dvs. date codificate ca JSON, iar dvs. decodificați datele în tipuri native, cum ar fi String
, Double
și Array
.
Când aplicația dvs. de rețete primește JSON (a se vedea mai sus), acesta poate fi apoi decodificat într-o structură Swift, ca aceasta:
struct Recipe { var name: String var author: String var url: URL var yield: Int var ingredients: var instructions: String}
În Swift, protocolul Codable
este utilizat pentru a trece de la un obiect de date JSON la o clasă sau structură Swift reală. Acest lucru se numește decodare, deoarece datele JSON sunt decodate într-un format pe care Swift îl înțelege. De asemenea, funcționează și în sens invers: codificarea obiectelor Swift ca JSON.
Protocolul Codable
din Swift este de fapt un alias al protocoalelor Decodable
și Encodable
. Deoarece deseori folosiți codificarea și decodificarea împreună, folosiți protocolul Codable
pentru a obține ambele protocoale dintr-o dată.
Piesa centrală a fluxului de lucru de codificare/decodificare este protocolul Codable
din Swift. Haideți să aflăm cum funcționează, în continuare!
Nu puteți face diferența între „codificare” și „decodificare”? Gândiți-vă la asta în felul următor: Convertim date din și în „cod”, ca o mașină Enigma sau un cifru secret de criptare. Codificarea înseamnă convertirea datelor în cod; en-coding, sau „în/în cod”. Decodarea înseamnă convertirea codului în date; de-codare, sau „din/în afara codului”.
Înființează-te ca dezvoltator iOS
Învață să construiești aplicații iOS 14 cu Swift 5
Înscrie-te la cursul meu de dezvoltare iOS și învață cum să-ți începi cariera ca dezvoltator iOS profesionist.
Protocolul Codable Explicat
Utilizarea JSON înainte de Swift 4 a fost un pic de o PITA. Trebuia să serializați singur JSON-ul cu JSONSerialization
, iar apoi să tipăriți fiecare proprietate din JSON în tipul Swift potrivit.
let json = try? JSONSerialization.jsonObject(with: data, options: )if let recipe = json as? { if let yield = recipe as? Int { recipeObject.yield = yield }}
Scurt de mai sus preia doar o valoare yield
din JSON și o transformă în Int
. Este extrem de verbos și este greu de răspuns corespunzător la eventualele erori și discrepanțe de tip. Chiar dacă funcționează, nu este ideal.
Bibliotecile precum SwiftyJSON facilitează foarte mult lucrul cu JSON, dar tot trebuie să mapezi datele JSON la obiectele și proprietățile lor Swift respective.
Cu Swift 4, și mai târziu, puteți folosi în schimb protocolul Codable
. Structura sau clasa dvs. Swift trebuie doar să adopte protocolul Codable
și veți obține codificarea și decodificarea JSON din start, gratuit.
Protocolul Codable
este o compoziție a două protocoale, Decodable și Encodable. Ambele protocoale sunt destul de minimale; ele definesc doar funcțiile init(from: Decoder)
și, respectiv, encode(to: Encoder)
. Cu alte cuvinte, aceste funcții înseamnă că, pentru ca un tip să fie „decodabil” sau „codabil”, va trebui să „decodeze de la ceva” și să „codifice la ceva”.
Reala magie a Codable
se întâmplă cu protocoalele Decoder
și Encoder
. Aceste protocoale sunt adoptate de componentele care codifică/decodifică diverse formate de date, cum ar fi JSON. În Swift, avem câteva -codificatoare:
-
PropertyListEncoder
șiPropertyListDecoder
pentru listele de proprietăți .plist -
JSONEncoder
șiJSONDecoder
pentru JSON – asta suntem noi! - NSKeyedArchiver poate lucra cu
Codable
, prinPropertyListEncoder
,Data
șiNSCoding
Classele JSONDecoder
și JSONEncoder
utilizează protocoalele Decoder
și Encoder
pentru a furniza funcționalitatea de decodare/codificare JSON. Asta înseamnă, de asemenea, că vă puteți scrie propriul codificator/decodificator personalizat pentru Codable
, cu condiția să adoptați protocoalele Decoder
și Encoder
!
Vreți o reîmprospătare a protocoalelor în Swift? Citiți Protocoale în Swift Explained pentru a vă pune la curent.
Lucrul cu Codable
Să ne uităm la un exemplu. Vom cartografia niște date JSON într-o structură Swift. În esență, decodificăm JSON într-un obiect Swift real.
În primul rând, creăm un struct numit User
. Cam așa:
struct User: Codable { var first_name: String var last_name: String var country: String}
Structul User
are trei proprietăți simple de tip String
și este conform cu protocolul Codable
.
Apoi, să scriem un pic de JSON. Acesta este JSON-ul cu care vom lucra:
{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}
Datele JSON intră de obicei în aplicația dvs. ca răspuns la o cerere de serviciu web sau printr-un fișier .json
, dar pentru acest exemplu vom pune pur și simplu JSON-ul într-un String
. Astfel:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Nota: Codul de mai sus utilizează ghilimelele triple """
pentru a crea șiruri de mai multe linii. Neat!
Ceea ce vom face în continuare, este să decodăm JSON-ul și să-l transformăm într-un obiect User
. Cam așa:
let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe
Iată ce se întâmplă:
- În primul rând, transformați
jsonString
într-un obiectData
prin apelarea funcțieidata(using:)
asupra șirului. Acesta este un pas intermediar necesar. - Apoi, creați un obiect
JSONDecoder
și apelați imediat funcțiadecode(_:from:)
asupra acestuia. Aceasta transformăjsonData
într-un obiect de tipUser
, prin decodificarea JSON. TipulUser.self
este furnizat ca parametru. - În cele din urmă, se tipărește numele de familie al utilizatorului cu
print(user.last_name)
. Această valoareuser
are ca tipUser
, deci este un obiect Swift real!
Facil, nu? În esență, ați „cartografiat” obiectul JSON într-o structură Swift și ați decodificat formatul JSON într-un obiect nativ cu care Swift poate lucra.
În codul de mai sus, ignorăm orice erori aruncate de decode(_:from:)
cu try!
. În propriul cod, va trebui să gestionați erorile cu un bloc do-try-catch
. O eroare care poate fi aruncată, de exemplu, provine din furnizarea unui JSON invalid.
User.self
este un metatype, o referință la tipul User
însuși. Îi spunem decodificatorului că dorim să decodificăm datele cu structura User
, furnizându-i o referință la acel tip.
O eroare frecventă atunci când vă definiți tipul Codable
, ca și structura User
, sunt cheile și proprietățile nepotrivite. Dacă ați adăugat o proprietate la struct, care nu este prezentă în JSON sau are un tip diferit, veți obține o eroare la decodarea JSON. Opusul, adică o proprietate care nu există în structura dumneavoastră, dar există în JSON, nu reprezintă o problemă. Cel mai simplu este să depanați această problemă proprietate pe rând, adică să continuați să adăugați proprietăți până când JSON nu mai decodifică fără erori. De asemenea, puteți determina ce proprietăți/chei să includeți sau să ignorați cu CodingKeys
enum (a se vedea mai jos).
Decodarea JSON în obiecte Swift cu Codable
Să ne uităm din nou la exemplul din secțiunea anterioară și să îl extindem. Iată JSON-ul cu care lucrăm:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
În continuare îl transformăm într-un obiect Data
. Astfel:
let jsonData = jsonString.data(using: .utf8)!
Este un pas intermediar necesar. În loc să reprezentăm JSON-ul ca un șir de caractere, acum stocăm JSON-ul ca un obiect nativ Data
. Codificarea caracterelor pe care o folosim pentru acel șir de caractere este UTF8.
Dacă vă uitați cu atenție, veți vedea că codul de mai sus folosește desfacerea forțată pentru a lucra cu valoarea opțională de returnare din data(using:)
. Haideți să despachetăm mai elegant acea valoare opțională!
if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }
Cu codul de mai sus putem răspunde la erori atunci când data(using:)
returnează nil
. Ați putea, de exemplu, să afișați un mesaj de eroare sau să lăsați în tăcere sarcina să eșueze și să salvați informațiile de diagnosticare într-un jurnal.
În continuare, vom crea un nou obiect JSONDecoder
. Astfel:
let decoder = JSONDecoder()
Apoi folosim acel decodor pentru a decoda datele JSON. Astfel:
let user = try! decoder.decode(User.self, from: jsonData)
Dar, funcția decode(_:from:)
poate arunca erori. Codul de mai sus se blochează ori de câte ori se întâmplă acest lucru. Din nou, dorim să răspundem la orice eroare care s-ar putea întâmpla. Astfel:
do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}
Și întregul bloc de cod arată acum după cum urmează. Vedeți în ce fel este diferit?
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) }}
Nu tăceți niciodată erorile. Prindeți eroarea și răspundeți la ea, fie cu UI/UX, prin reluarea sarcinii, fie prin logare, blocarea și repararea ei.
Lucrul cu CodingKeys
Ce se întâmplă dacă proprietățile noastre JSON, cum ar fi first_name
și/sau firstName
, sunt diferite de proprietățile structurilor Swift? Aici intervine CodingKeys
.
Care clasă sau struct care se conformează la Codable
poate declara o enumerare specială imbricata numită CodingKeys
. O utilizați pentru a defini proprietățile care ar trebui să fie codificate și decodificate, inclusiv numele lor.
Să ne uităm la un exemplu. În structura User
de mai jos, am schimbat numele proprietăților din snake_case în camelCase. De exemplu, cheia first_name
se numește acum firstName
.
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 }}
Când folosiți structura de mai sus cu exemplele din secțiunile anterioare, veți vedea că puteți folosi un obiect User
cu noile nume de proprietăți. Astfel:
print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom
Enumerarea CodingKeys
își mapează cazurile în proprietăți și utilizează valorile șirurilor de caractere pentru a selecta numele corecte ale proprietăților în datele JSON. Cazul din enumerație este numele proprietății pe care doriți să o utilizați în struct, iar valoarea sa este numele cheii din datele JSON.
Codificarea obiectelor Swift ca JSON cu Codable
Până acum ne-am concentrat pe decodificarea datelor JSON în obiecte Swift. Cum rămâne cu mersul în sens invers? Putem, de asemenea, să codificăm obiecte ca JSON? Da, de ce nu!
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)
Și ieșirea este:
{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}
Ce se întâmplă aici?
- În primul rând, se creează un obiect
User
și se atribuie niște valori proprietăților sale - Apoi, se folosește
encode(_:)
pentru a codifica obiectuluser
într-un obiect JSONData
- În final, convertiți obiectul
Data
înString
și îl imprimați
Dacă vă uitați cu atenție, veți vedea că codificarea urmează aceiași pași ca și decodificarea, doar că în sens invers. Trecând de la obiectul Swift, prin decodor, rezultând un șir JSON.
La fel ca înainte, putem extinde exemplul pentru a trata erorile? Da! Astfel:
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)}
În exemplul de mai sus, folosim, de asemenea, proprietatea outputFormatting
a codificatorului pentru a „tipări frumos” datele JSON. Aceasta adaugă spații, tabulări și linii noi pentru a face șirul JSON mai ușor de citit. Astfel:
{ "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}
Frumos!
Este demn de remarcat faptul că dicționarele în Swift, și în JSON, nu au o ordine de sortare fixă. Acesta este motivul pentru care veți vedea ordine de sortare diferite pentru cheile country
, first_name
etc. din JSON, dacă executați codul de mai sus de câteva ori. Cele mai multe servicii web care emit JSON vor impune o ordine de sortare fixă printr-o schemă, ceea ce cu siguranță ușurează citirea și navigarea în JSON.
Lucrul cu matrici imbricate și Codable
Până acum am lucrat cu obiecte JSON simple – doar o clasă cu câteva proprietăți. Dar ce se întâmplă dacă datele cu care lucrați sunt mai complexe?
De exemplu, datele JSON pe care le puteți primi înapoi de la un serviciu web Twitter sau Facebook sunt adesea imbricate. Adică, datele la care doriți să ajungeți sunt îngropate în 2-3 niveluri de matrici și dicționare JSON. Ce facem acum!?
Să ne uităm mai întâi la un exemplu simplu. Iată structura Swift cu care vom lucra:
struct User: Codable { var first_name: String var last_name: String}
După aceea, iată datele JSON pe care vrem să le decodificăm:
let jsonString = """"""
Dacă vă uitați cu atenție, vedeți că datele de care avem nevoie sunt stocate sub forma unui array. JSON-ul nu este un obiect simplu, ci este un array cu 3 obiecte User
. Știm că este un array de obiecte, datorită parantezelor pătrate (array) și parantezelor mâzgălite
{ }
(obiect). Elementele multiple sunt întotdeauna separate prin virgule.
Cum putem decoda aceste obiecte User
? Iată cum:
let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}
Scurt de cod de mai sus este extrem de asemănător cu ceea ce ați mai văzut până acum, dar există o diferență importantă. Vedeți tipul pe care îl furnizăm funcției decode(_:from:)
? Tipul este (cu .self), sau „array of User objects”. În loc să lucrăm cu un singur obiect
User
, vrem să decodificăm o grămadă de obiecte, iar acest lucru este desemnat cu tipul array.
În continuare, vom discuta despre cum puteți lucra cu tipuri imbricate mai complexe. Să considerăm, de exemplu, că matricea de obiecte User
este imbricata în interiorul unui dicționar JSON. Astfel:
let jsonString = """{ "users": }"""
În fragmentul JSON de mai sus, elementul de nivel superior este un dicționar (sau „obiect”). Acesta are doar o singură pereche cheie-valoare: o matrice users
. Datele pe care le dorim se află în interiorul acestui array. Cum ajungem la ele?
Este important ca abordarea noastră să nu fie prea complicată. Este ușor să credeți că veți avea nevoie de o analiză JSON avansată, dar se pare că, de fapt, putem, de asemenea, anina structurile Swift. Vom descrie întregul JSON ca fiind un struct, cu struct-ul User
în interiorul acestuia.
Iată ce se întâmplă:
struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }
Iată ce se întâmplă:
- Dicționarul JSON de nivel superior corespunde tipului
Response
. La fel ca înainte, acest tip este conform cuCodable
, pentru a suporta codificarea și decodificarea JSON. - În interiorul acestui struct am definit un alt struct numit
User
. Acesta este exact același tip pe care l-am folosit înainte. Acest struct este „imbricat”. - Structul
Response
are o proprietate numităusers
de tip, sau array de obiecte User.
Ceea mai tare este că, din punct de vedere semantic, ambele structuri de date JSON și Swift sunt exact la fel. Ele folosesc doar o sintaxă diferită. Am definit o matrice imbricata în interiorul dicționarului de nivel superior, la fel cum structura Response
are o structură User
și o proprietate users
imbricate în interiorul ei.
Făcând-o să funcționeze este floare la ureche acum. Uitați-vă la asta:
let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}
Vezi cum folosim tipul Response
pentru a decoda JSON, iar apoi facem o buclă peste proprietatea response.users
? Verificați asta cu structura Response
și cu JSON. Preluăm perechea cheie-valoare users
din dicționarul de nivel superior și mapăm obiectele din interior în obiecte User
. Frumos!
Utilizarea tipurilor imbricate este o abordare excelentă de uz general pentru structurile de date JSON complexe. Când dați peste o bucată de JSON pe care nu o puteți reprezenta cu ușurință într-un tip tangibil, cum ar fi User
sau Tweet
, încercați să extindeți tipul la ceva de genul Response
sau UserCollection
. În loc să mergeți mai adânc, mergeți mai larg și încorporați întregul JSON. Utilizați tipuri imbricate pentru a ajunge la datele pe care le doriți. De asemenea, merită menționat aici că nu trebuie să adăugați structura User
în structura Response
– poate merge și în afara acesteia.
Să te angajezi ca dezvoltator iOS
Învață să construiești aplicații iOS 14 cu Swift 5
Înscrie-te la cursul meu de dezvoltare iOS și învață cum să-ți începi cariera ca dezvoltator iOS profesionist.
Lecturi suplimentare
Și asta e tot! Acum știți:
- Cum să folosiți
Codable
pentru a crea obiecte care pot fi codificate și decodificate - Cum să folosiți
JSONDecoder
șiJSONEncoder
pentru a codifica și decodifica obiecte JSON și omologii lor Swift - Cum se utilizează codificarea și decodificarea, și de ce este importantă în dezvoltarea iOS de zi cu zi
- Cum să lucrați cu date JSON mai complexe și cum să folosiți tipurile imbricate
Vreți să învățați mai mult? Consultați aceste resurse:
- Array-uri, dicționare și structuri
- Lucrul cu JSON în Swift cu SwiftyJSON
- Introducere la programarea orientată pe obiecte în Swift
- Cum să: Găsirea unui element într-o matrice în Swift
- Cum să folosiți documentația Apple pentru dezvoltatori pentru distracție și profit
- Cum să găsiți șiruri de caractere cu expresii regulate în Swift
- De ce Arhitectura aplicațiilor contează
- Strings in Swift Explained
- Working with Files on iOS with Swift
- Storing Data with NSCoding and NSKeyedArchiver