Сериализация объектов в Swift: Codable, NSCoding и JSONEncoder

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Сериализация объектов в Swift (iOS/macOS)

Сериализация — это процесс преобразования сложных объектов (структур, классов, экземпляров) в формат, удобный для хранения или передачи по сети (JSON, Property List, бинарные данные). Десериализация — обратный процесс восстановления объекта из этих данных. В Swift для iOS и macOS существуют два основных подхода: современный протокол Codable (и его составляющие Encodable и Decodable) и более старый NSCoding из фреймворка Foundation. В этой статье мы подробно разберем оба подхода с примерами кода.

1. Протокол Codable (рекомендованный способ)

Codable — это протокол, объединяющий Encodable и Decodable. Он позволяет автоматически генерировать код для сериализации и десериализации, если все свойства вашей структуры или класса также соответствуют Codable (строки, числа, даты, массивы, словари и т.д.).

1.1. Базовая сериализация в JSON

Рассмотрим простую структуру пользователя:

import Foundation

struct User: Codable {
    let id: Int
    var name: String
    var email: String
}

// Создаем экземпляр
let user = User(id: 1, name: "Иван Петров", email: "ivan@example.com")

// Сериализация в Data (JSON)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // для читаемого вывода

do {
    let jsonData = try encoder.encode(user)
    if let jsonString = String(data: jsonData, encoding: .utf8) {
        print(jsonString)
    }
} catch {
    print("Ошибка сериализации: \(error.localizedDescription)")
}

1.2. Десериализация из JSON

// Предположим, у нас есть JSON-строка
let jsonString = """
{
    "id": 2,
    "name": "Мария Смирнова",
    "email": "maria@test.com"
}
"""

let jsonData = Data(jsonString.utf8)
let decoder = JSONDecoder()

do {
    let decodedUser = try decoder.decode(User.self, from: jsonData)
    print("Имя: \(decodedUser.name), Email: \(decodedUser.email)")
} catch {
    print("Ошибка десериализации: \(error.localizedDescription)")
}

1.3. Настройка ключей (CodingKeys)

Если имена свойств в Swift отличаются от ключей в JSON, используйте перечисление CodingKeys:

struct Article: Codable {
    var title: String
    var body: String
    var publicationDate: Date

    enum CodingKeys: String, CodingKey {
        case title
        case body
        case publicationDate = "published_at" // ключ в JSON
    }
}

let articleJSON = """
{"title":"Swift Codable","body":"Текст статьи","published_at":"2024-01-15T10:00:00Z"}
"""
let articleData = Data(articleJSON.utf8)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601 // для корректного парсинга даты

if let article = try? decoder.decode(Article.self, from: articleData) {
    print("Заголовок: \(article.title), Дата: \(article.publicationDate)")
}

1.4. PropertyListEncoder / PropertyListDecoder

Для сериализации в формат Property List (XML или бинарный) используйте PropertyListEncoder:

let plistEncoder = PropertyListEncoder()
plistEncoder.outputFormat = .xml

if let plistData = try? plistEncoder.encode(user) {
    // plistData можно сохранить в файл
    let plistString = String(data: plistData, encoding: .utf8)
    print(plistString ?? "Некорректные данные")
}

2. Протокол NSCoding (для классов, наследуемых от NSObject)

NSCoding — это протокол из Objective-C, который до сих пор используется для сериализации классов, особенно тех, что работают с UIKit/AppKit (например, UIView, UIViewController). Он требует, чтобы класс наследовал от NSObject и реализовывал методы encode(with:) и init?(coder:).

2.1. Пример класса с NSCoding

import Foundation

class Person: NSObject, NSCoding {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // MARK: - NSCoding
    func encode(with coder: NSCoder) {
        coder.encode(name, forKey: "name")
        coder.encode(age, forKey: "age")
    }

    required init?(coder: NSCoder) {
        guard let name = coder.decodeObject(forKey: "name") as? String else {
            return nil
        }
        self.name = name
        self.age = coder.decodeInteger(forKey: "age")
    }
}

// Сериализация
let person = Person(name: "Алексей", age: 30)
let data = NSKeyedArchiver.archived                    

Рекомендаций