Почему Swift — это не только про UI, но и про файлы
Когда разработчики начинают изучать Swift, они чаще всего пишут приложения с интерфейсами, анимациями и сетевыми запросами. Но рано или поздно возникает задача: сохранить данные на устройстве, прочитать конфиг, распарсить JSON-файл или просто скопировать документ. И тут многие теряются. А зря — работа с файлами в Swift настолько элегантна и продумана, что после неё в другие языки возвращаться не хочется.
В этой статье мы разберём взаимодействие с файловой системой в Swift от простого к сложному. Вы узнаете, как создавать, читать, записывать и удалять файлы, работать с директориями, обрабатывать ошибки и даже шифровать данные. Поехали!
Основы: FileManager и FileHandle
Сердце файловой системы в Swift — класс FileManager. Это «диспетчер», который знает всё о папках и файлах на устройстве. Он позволяет проверять существование, перемещать, копировать и удалять объекты. Для работы с содержимым файлов (чтение и запись) используется FileHandle или более высокоуровневые методы Data, String и Codable.
Где хранить файлы? Пути и директории
Прежде чем писать код, нужно понять, куда мы имеем право сохранять данные. В iOS и macOS есть строгие правила: приложение может писать только в свою песочницу (sandbox). Самые популярные директории:
- Documents — для пользовательских данных, которые должны сохраняться при бэкапах.
- Library/Caches — для временных файлов, которые система может удалить.
- tmp — для одноразовых файлов, удаляется при перезагрузке.
Получить путь к нужной папке проще простого:
import Foundation
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
print("Documents: \(documentsURL.path)")
let cachesURL = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
print("Caches: \(cachesURL.path)")
Создание и удаление файлов в Swift
Начнём с самого базового — создадим пустой файл в директории Documents:
let fileURL = documentsURL.appendingPathComponent("myFile.txt")
// Создаём файл, если его нет
if !fileManager.fileExists(atPath: fileURL.path) {
fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
print("Файл создан!")
}
Обратите внимание: createFile(atPath:contents:attributes:) возвращает Bool, который сообщает, удалось ли создать файл. Всегда проверяйте результат — это спасёт от неожиданных ошибок.
Удаление файла делается одной строкой:
do {
try fileManager.removeItem(at: fileURL)
print("Файл удалён")
} catch {
print("Ошибка удаления: \(error.localizedDescription)")
}
Здесь мы используем try и блок do-catch — стандартный подход для обработки ошибок в Swift. Никогда не вызывайте removeItem без проверки, иначе приложение упадёт.
Чтение и запись текстовых файлов
Самый частый сценарий — работа с текстовыми данными. Swift предлагает удобные методы для чтения файла Swift и записи через строки:
// Запись текста
let text = "Привет, мир! Hello, Swift!"
do {
try text.write(to: fileURL, atomically: true, encoding: .utf8)
print("Текст записан")
} catch {
print("Ошибка записи: \(error)")
}
// Чтение текста
do {
let savedText = try String(contentsOf: fileURL, encoding: .utf8)
print("Прочитано: \(savedText)")
} catch {
print("Ошибка чтения: \(error)")
}
Флаг atomically: true гарантирует, что запись будет атомарной — сначала данные пишутся во временный файл, потом переименовываются. Это предотвращает повреждение файла при сбоях.
Работа с бинарными данными: изображения, архивы
Иногда нужно сохранить не текст, а массив байт — например, изображение или зашифрованные данные. Для этого используем Data:
// Создаём бинарные данные (например, массив байт)
let binaryData = Data([0x48, 0x65, 0x6C, 0x6C, 0x6F]) // "Hello"
let binaryURL = documentsURL.appendingPathComponent("binary.dat")
do {
try binaryData.write(to: binaryURL, options: .atomic)
print("Бинарные данные записаны")
// Чтение обратно
let loadedData = try Data(contentsOf: binaryURL)
print("Загружено \(loadedData.count) байт")
} catch {
print("Ошибка с бинарными данными: \(error)")
}
Для работы с изображениями (UIImage на iOS) нужно конвертировать их в Data через pngData() или jpegData(compressionQuality:).
JSON — король форматов для Swift
Работа с JSON в Swift заслуживает отдельного абзаца. Благодаря протоколу Codable сериализация превращается в магию. Допустим, у нас есть структура пользователя:
struct User: Codable {
let name: String
let age: Int
let isActive: Bool
}
let users = [
User(name: "Анна", age: 28, isActive: true),
User(name: "Борис", age: 35, isActive: false)
]
let jsonURL = documentsURL.appendingPathComponent("users.json")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let jsonData = try encoder.encode(users)
try jsonData.write(to: jsonURL)
print("JSON записан")
// Чтение JSON
let loadedData = try Data(contentsOf: jsonURL)
let decoder = JSONDecoder()
let loadedUsers = try decoder.decode([User].self, from: loadedData)
print("Загружено \(loadedUsers.count) пользователей")
print(loadedUsers[0].name) // Анна
} catch {
print("Ошибка работы с JSON: \(error)")
}
Обратите внимание: JSONEncoder автоматически конвертирует поля camelCase в snake_case, если задать keyEncodingStrategy. Это невероятно удобно при работе с серверными API.
Управление директориями: создаём свою файловую структуру
Часто нужно организовать файлы по папкам. Swift FileManager позволяет создавать вложенные директории одной командой:
let imagesDir = documentsURL.appendingPathComponent("Images")
let subDir = imagesDir.appendingPathComponent("2024")
do {
// Создаём все недостающие папки по пути
try fileManager.createDirectory(at: subDir, withIntermediateDirectories: true, attributes: nil)
print("Директория создана: \(subDir.path)")
// Пол