Codable

Uma das novidades do Swift 4 foi a introdução do arquivamento e serialização de tipos nativos, com isso podemos salvar nossos tipos criados na Swift com NSCoding por exemplo. Porém a melhor parte é que também podemos utilizar o NSPropertyListSerialization e o NSJSONSerialization para transformar em Property List e JSON respectivamente.

Suponha que nosso modelo de dados é uma struct que representa uma temperatura, composta por valor e escala:

struct Temperatura {
    enum Escala: String {
        case celsius    = "°C"
        case fahrenheit = "°F"
    }

    var valor: Float
    var escala: Escala
}

Para podermos aplicar transformações em nossa estrutura precisamos conformar com o protocolo Codable. Como todos os tipos utilizados são nativos, só precisamos declarar a conformidade:

struct Temperatura: Codable {
    enum Escala: String, Codable {

Vamos aproveitar e criar uma lista de temperaturas para poder transformar mais tarde:

let previsao = [
    Temperatura(valor: 15, escala: .celsius),
    Temperatura(valor: 13, escala: .celsius),
    Temperatura(valor: 21, escala: .fahrenheit)
]

Encoding

Agora que temos nosso modelo conformando com o protocolo Codable podemos transformá-lo em algum formato para armazenar em disco ou enviar à um servidor.

Uma das maneiras de armazenar em disco é com Property List e para isso usaremos o PropertyListEncoder:

let plistEncoder = PropertyListEncoder()

// como o formato padrão para escrita de plists é binário,
// vamos alterá-lo para ficar mais fácil de mostrar a transformação
// no código final essa linha pode ser removida
plistEncoder.outputFormat = .xml

let plistData = try plistEncoder.encode(previsao)

Nota: O método .encode(_) retorna um valor do tipo Data, portanto para ver o conteúdo de forma legível precisamos criar uma String:

print(String(data: plistData, encoding: .utf8))

Resultado:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>escala</key>
        <string>°C</string>
        <key>valor</key>
        <real>15</real>
    </dict>
    <dict>
        <key>escala</key>
        <string>°C</string>
        <key>valor</key>
        <real>13</real>
    </dict>
    <dict>
        <key>escala</key>
        <string>°F</string>
        <key>valor</key>
        <real>21</real>
    </dict>
</array>
</plist>

Para transformar em JSON é só usar o JSONEncoder:

let jsonEncoder = JSONEncoder()

// como o formato padrão para escrita de JSON é compacto,
// vamos alterá-lo para ficar mais fácil de mostrar a transformação
// no código final essa linha pode ser removida
jsonEncoder.outputFormatting = .prettyPrinted

let jsonData = try jsonEncoder.encode(previsao)

Nota: O método .encode(_) retorna um valor do tipo Data, portanto para ver o conteúdo de forma legível precisamos criar uma String:

print(String(data: jsonData, encoding: .utf8))

Resultado:

[{
    "valor": 15,
    "escala": "°C"
}, {
    "valor": 13,
    "escala": "°C"
}, {
    "valor": 21,
    "escala": "°F"
}]

Decoding

Metade do trabalho já foi feito, transformamos tipos nativos para plist e JSON, agora vamos fazer o contrário. Isso vai facilitar muito a comunicação entre servidor e aplicativo, tornando o código mais simples e mais seguro. Para realizar a decodificação o decoder precisa saber qual o tipo que deve tentar transformar, nesse caso [Temperatura].self, que representa uma lista de Temperaturas.

Para isso é só utilizar PropertyListDecoder:

let plistDecoder = PropertyListDecoder()
let plistDecoded = try plistDecoder.decode([Temperatura].self, from: plistData)

e JSONDecoder:

let jsonDecoder = JSONDecoder()
let jsonDecoded = try jsonDecoder.decode([Temperatura].self, from: jsonData)

Exemplo Completo

//: Swift 4: Codable
//: ===
import Foundation

struct Temperatura: Codable {
    enum Escala: String, Codable {
        case celsius    = "°C"
        case fahrenheit = "°F"
    }

    var valor: Float
    var escala: Escala
}

let previsao = [
    Temperatura(valor: 15, escala: .celsius),
    Temperatura(valor: 13, escala: .celsius),
    Temperatura(valor: 21, escala: .fahrenheit)
]



//: Encoding
//: ---

//: - plist
let plistEncoder = PropertyListEncoder()
plistEncoder.outputFormat = .xml
let plistData = try plistEncoder.encode(previsao)
String(data: plistData, encoding: .utf8)

//: - JSON
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(previsao)
String(data: jsonData, encoding: .utf8)



//: Decoding
//: ---

//: - plist
let plistDecoder = PropertyListDecoder()
let plistDecoded = try plistDecoder.decode([Temperatura].self, from: plistData)

//: - JSON
let jsonDecoder = JSONDecoder()
let jsonDecoded = try jsonDecoder.decode([Temperatura].self, from: jsonData)

ou faça o download do Playground.

Até a próxima! >}