🍎 Swift

Работа с файлами в Swift: подробное руководство

Что это такое и зачем? (аналогия из реальной жизни)

Представь, что ты пишешь сочинение в тетради. Пока ты пишешь — информация есть в твоей голове и на кончике ручки. Но стоит закрыть тетрадь и убрать её в рюкзак — информация сохранилась. На следующий день ты можешь открыть тетрадь, прочитать написанное и дописать что-то новое.

Файл в программировании — это та же самая тетрадь. Только вместо ручки у нас код, а вместо страниц — байты и символы.

Зачем нам, программистам, уметь работать с файлами? Причин много:

  • Сохранять данные между запусками программы (например, список дел, рекорды в игре, настройки пользователя).
  • Читать готовую информацию (конфигурации, словари, базы знаний).
  • Создавать отчёты и логи — записывать, что делала программа, чтобы потом найти ошибки.
  • Обмениваться данными с другими программами (экспорт в CSV, импорт из JSON).
💡 Важно: Без работы с файлами любая программа — как рыба без воды. Она живёт, только пока работает, и умирает, теряя всё, при закрытии.

В этом уроке мы научимся читать файлы на языке Swift. Чтение — это основа. Прежде чем научиться писать, нужно научиться понимать, что уже написано.

Теория: как это работает изнутри

Давай заглянем «под капот» и разберёмся, что происходит, когда программа читает файл.

Путь к файлу (File Path)

Каждый файл в компьютере имеет свой адрес — путь. Как у дома есть улица и номер, так у файла есть папки и имя.

Пути бывают:

  • Абсолютные — полный адрес от корня диска: /Users/ivan/Documents/notes.txt
  • Относительные — адрес относительно текущей папки: Documents/notes.txt

Потоки данных (Streams)

Когда программа читает файл, она открывает поток — воображаемую трубу, по которой данные текут из файла в память. Поток может быть:

  • Текстовым — читаем символы и строки (например, .txt, .csv, .json)
  • Бинарным — читаем байты (например, .jpg, .mp3, .pdf)

Кодировки (Encodings)

Компьютер хранит всё как числа. Чтобы превратить числа в буквы, нужна таблица соответствия — кодировка. Самая популярная сегодня — UTF-8. Она понимает и русские буквы, и иероглифы, и смайлики.

⚠️ Ошибка новичка: Если прочитать файл в одной кодировке, а он записан в другой — получишь «кракозябры» вместо текста. Всегда проверяй кодировку!

Режимы чтения

Можно читать файл по-разному:

  • Целиком — загрузить всё содержимое в память сразу. Быстро, но если файл огромный (10 ГБ) — программа упадёт.
  • Построчно — читать строку за строкой. Экономно для больших файлов.
  • Порциями (чанками) — читать по N байт. Гибко, но сложнее.

Жизненный цикл файла в программе

  1. Открыть — получить доступ к файлу через путь.
  2. Прочитать — получить данные из файла в переменную.
  3. Закрыть — освободить ресурсы (в Swift это часто делается автоматически).

Синтаксис в Swift: все формы и варианты

В Swift есть несколько способов читать файлы. Мы рассмотрим самые популярные и удобные.

1. Класс String — самый простой способ

Если файл небольшой и текстовый, можно прочитать его одной строкой кода:

// Создаём путь к файлу
let path = "/Users/ivan/Desktop/hello.txt"

// Пытаемся прочитать файл как строку в кодировке UTF-8
let content = try String(contentsOfFile: path, encoding: .utf8)

// Теперь в переменной content — весь текст файла
print(content)

2. Класс Data — для бинарных данных

Если нужно прочитать файл как набор байт (например, картинку):

let path = "/Users/ivan/Desktop/image.jpg"
let data = try Data(contentsOf: URL(fileURLWithPath: path))
print("Прочитано (data.count) байт")

3. Класс FileHandle — для продвинутого чтения

Позволяет читать файл частями, перемещаться по нему вперёд-назад:

let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
let data = fileHandle.readData(ofLength: 1024) // читаем первые 1024 байта
fileHandle.closeFile()

4. InputStream — для потокового чтения

Самый гибкий способ, используется для больших файлов и сетевых потоков:

let inputStream = InputStream(fileAtPath: path)
inputStream?.open()
var buffer = [UInt8](repeating: 0, count: 4096)
let bytesRead = inputStream?.read(&buffer, maxLength: buffer.count)
inputStream?.close()

Справочная таблица методов чтения

Метод Когда использовать Плюсы Минусы
String(contentsOfFile:) Маленькие текстовые файлы Просто, одна строка Загружает всё в память
Data(contentsOf:) Бинарные файлы любого размера Универсально Всё в памяти
FileHandle Нужен контроль над позицией Можно читать с середины Сложнее, нужно закрывать
InputStream Огромные файлы, сети Экономия памяти Много кода

Примеры кода — от простого к сложному

Пример 1: Читаем приветствие из файла

// Самый простой пример — читаем файл с одной строкой текста

import Foundation // Обязательно! Без него не работают файлы

// Путь к файлу на рабочем столе (замени на свой!)
let filePath = "/Users/ivan/Desktop/greeting.txt"

// do-catch — это защитная сетка. Если файла нет — программа не упадёт
do {
    // Пробуем прочитать файл
    let text = try String(contentsOfFile: filePath, encoding: .utf8)
    print("Содержимое файла: (text)")
} catch {
    // Если что-то пошло не так — печатаем ошибку
    print("Ошибка чтения файла: (error.localizedDescription)")
}

Пример 2: Читаем и считаем строки

// Полезно для логов и списков — узнаём, сколько строк в файле

import Foundation

let path = "/Users/ivan/Desktop/students.txt"

do {
    let content = try String(contentsOfFile: path, encoding: .utf8)
    
    // Разделяем текст на строки по символу новой строки 

    let lines = content.components(separatedBy: "
")
    
    print("Всего строк: (lines.count)")
    
    // Выводим каждую строку с номером
    for (index, line) in lines.enumerated() {
        print("Строка (index + 1): (line)")
    }
} catch {
    print("Не удалось прочитать файл: (error)")
}

Пример 3: Читаем файл по URL из интернета

// Swift умеет читать не только локальные файлы, но и файлы по ссылке

import Foundation

// Создаём URL-адрес (обрати внимание: это URL, а не путь!)
if let url = URL(string: "https://example.com/data.txt") {
    do {
        // Читаем содержимое по URL
        let content = try String(contentsOf: url, encoding: .utf8)
        print("Данные из интернета:")
        print(content.prefix(500)) // Покажем только первые 500 символов
    } catch {
        print("Ошибка загрузки: (error.localizedDescription)")
    }
}

Пример 4: Читаем бинарный файл (картинку) и проверяем размер

// Иногда нужно просто узнать размер файла или прочитать его как данные

import Foundation

let imagePath = "/Users/ivan/Desktop/photo.jpg"

do {
    // Читаем файл как Data (набор байт)
    let imageData = try Data(contentsOf: URL(fileURLWithPath: imagePath))
    
    print("Размер файла: (imageData.count) байт")
    print("Это примерно (imageData.count / 1024) КБ")
    
    // Первые 4 байта часто хранят сигнатуру формата
    if imageData.count >= 4 {
        let signature = imageData[0...3]
        print("Первые 4 байта: (signature)")
    }
} catch {
    print("Ошибка: (error)")
}

Пример 5: Читаем большой файл построчно (экономим память)

// Если файл огромный (например, 1 ГБ), читать его целиком нельзя — не хватит памяти
// Используем FileHandle для построчного чтения

import Foundation

let bigFilePath = "/Users/ivan/Desktop/big_log.txt"

// Создаём обработчик файла
guard let fileHandle = FileHandle(forReadingAtPath: bigFilePath) else {
    print("Не удалось открыть файл")
    exit(1) // Завершаем программу с ошибкой
}

// Буфер для накопления данных
var buffer = Data()
var lineCount = 0

// Читаем файл порциями по 4096 байт
while true {
    let chunk = fileHandle.readData(ofLength: 4096)
    
    // Если chunk пустой — значит, файл закончился
    if chunk.isEmpty {
        break
    }
    
    // Добавляем прочитанное в буфер
    buffer.append(chunk)
    
    // Ищем символы новой строки в буфере
    while let newlineIndex = buffer.firstIndex(of: 0x0A) { // 0x0A = 

        // Извлекаем строку от начала до символа 

        let lineData = buffer[0..

Пример 6: Читаем CSV-файл (таблицу) и разбираем на колонки

// CSV — это формат таблиц, где колонки разделены запятыми
// Часто используется для экспорта из Excel

import Foundation

let csvPath = "/Users/ivan/Desktop/students.csv"

do {
    let content = try String(contentsOfFile: csvPath, encoding: .utf8)
    let rows = content.components(separatedBy: "
")
    
    print("Найдено строк: (rows.count)")
    
    for (index, row) in rows.enumerated() {
        // Пропускаем пустые строки
        guard !row.isEmpty else { continue }
        
        // Разделяем строку на колонки по запятой
        let columns = row.components(separatedBy: ",")
        
        print("Строка (index + 1):")
        for (colIndex, value) in columns.enumerated() {
            print("  Колонка (colIndex + 1): (value.trimmingCharacters(in: .whitespaces))")
        }
    }
} catch {
    print("Ошибка: (error)")
}

Пример 7: Читаем JSON — современный формат данных

// JSON — самый популярный формат для обмена данными в интернете
// Swift умеет превращать JSON в свои структуры (Decodable)

import Foundation

// Сначала создаём структуру, которая соответствует JSON
struct Student: Decodable {
    let name: String
    let age: Int
    let grades: [Int]
}

let jsonPath = "/Users/ivan/Desktop/student.json"

do {
    // Читаем файл как Data
    let jsonData = try Data(contentsOf: URL(fileURLWithPath: jsonPath))
    
    // Превращаем JSON в массив студентов
    let students = try JSONDecoder().decode([Student].self, from: jsonData)
    
    // Теперь работаем с объектами, а не с текстом!
    for student in students {
        print("Студент: (student.name), возраст: (student.age)")
        print("Оценки: (student.grades)")
        let average = Double(student.grades.reduce(0, +)) / Double(student.grades.count)
        print("Средний балл: (String(format: "%.1f", average))")
        print("---")
    }
} catch {
    print("Ошибка парсинга JSON: (error)")
}

Сравнение с Python: чем похоже и чем отличается

Если ты уже знаком с Python, тебе будет проще. Давай сравним подходы.

Операция Swift Python
Чтение всего файла String(contentsOfFile:) open().read()
Чтение построчно FileHandle + буфер for line in file:
Обработка ошибок do-catch try-except
Кодировка Указывается параметром encoding: Указывается в open(encoding=)
Закрытие файла Автоматическое (ARC) или closeFile() Автоматическое (with) или close()
Работа с JSON JSONDecoder + Decodable json.load()
🔑 Главное отличие: Swift строже относится к типам и ошибкам. В Python можно прочитать файл одной строкой, и если что-то пойдёт не так — программа просто упадёт. Swift заставляет тебя обрабатывать ошибки через try и catch. Это кажется сложным, но делает код надёжнее.

Типичные ошибки и подводные камни

Ошибка 1: Файл не найден

Самая частая ошибка. Ты указываешь путь, а файла там нет.

// ❌ Неправильно: без обработки ошибок
let text = try String(contentsOfFile: "no_such_file.txt")

// ✅ Правильно: используем do-catch
do {
    let text = try String(contentsOfFile: "no_such_file.txt")
} catch {
    print("Файл не найден!")
}

Ошибка 2: Неправильная кодировка

Файл в Windows-1251, а ты читаешь в UTF-8 — получишь кракозябры.

// ❌ Неправильно: предполагаем UTF-8
let text = try String(contentsOfFile: path, encoding: .utf8)

// ✅ Правильно: пробуем разные кодировки
let encodings: [String.Encoding] = [.utf8, .windowsCP1251, .ascii]
for encoding in encodings {
    if let text = try? String(contentsOfFile: path, encoding: encoding) {
        print("Успешно прочитано в кодировке (encoding)")
        break
    }
}

Ошибка 3: Забыли import Foundation

Без этого импорта работа с файлами недоступна.

// ❌ Неправильно: нет импорта
let text = try String(contentsOfFile: "test.txt") // Ошибка компиляции!

// ✅ Правильно: добавляем import
import Foundation
let text = try String(contentsOfFile: "test.txt")

Ошибка 4: Чтение слишком большого файла целиком

Если файл весит 10 ГБ, а ты читаешь его в строку — программа вылетит.

// ❌ Опасно для больших файлов
let content = try String(contentsOfFile: "huge_file.txt")

// ✅ Правильно: используем построчное чтение
let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: "huge_file.txt"))
// ... читаем порциями

Ошибка 5: Не закрыли файл

В Swift это реже проблема (память очищается автоматически), но при работе с FileHandle лучше закрывать явно.

let handle = try FileHandle(forReadingFrom: url)
// ... работаем
handle.closeFile() // Не забываем!

Лучшие практики и советы профессионалов

1. Всегда используй do-catch

Даже если ты уверен, что файл существует. Внешние условия меняются: флешку вытащили, права доступа изменились, диск переполнился.

2. Используй URL вместо строк с путями

URL — более современный и безопасный способ. Он лучше работает с разными операционными системами.

// Плохо
let path = "/Users/ivan/file.txt"

// Хорошо
let url = URL(fileURLWithPath: "/Users/ivan/file.txt")

3. Для конфигураций используй Bundle

Если файл идёт в комплекте с приложением (не создаётся пользователем), используй Bundle.main:

if let path = Bundle.main.path(forResource: "config", ofType: "json") {
    let data = try Data(contentsOf: URL(fileURLWithPath: path))
}

4. Проверяй существование файла

Перед чтением можно проверить, есть ли файл:

let fileManager = FileManager.default
if fileManager.fileExists(atPath: path) {
    // Можно читать
} else {
    print("Файла нет, создаём...")
}

5. Для продакшена — используй кэширование

Если ты читаешь один и тот же файл много раз, сохрани его в память после первого чтения. Это ускорит программу в десятки раз.

6. Помни про песочницу (Sandbox)

На iOS и macOS приложения работают в изолированной среде. Ты не можешь читать файлы из чужих папок без специальных разрешений. Всегда используй FileManager для получения правильных путей:

let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsDirectory.appendingPathComponent("myfile.txt")

Вопросы для самопроверки

  1. Вопрос: Какой импорт нужно добавить в Swift-файл, чтобы работать с файлами?

    Ответ: import Foundation

  2. Вопрос: Что такое кодировка и почему она важна при чтении файлов?

    Ответ: Кодировка — это таблица соответствия чисел и символов. Если прочитать файл в неправильной кодировке, текст превратится в бессмысленный набор символов.

  3. Вопрос: Какая конструкция в Swift используется для обработки ошибок при работе с файлами?

    Ответ: do { try ... } catch { ... }

  4. Вопрос: Чем отличается чтение через String(contentsOfFile:) от чтения через FileHandle?

    Ответ: Первый способ загружает весь файл в память сразу (просто, но опасно для больших файлов). Второй позволяет читать порциями и контролировать позицию.

  5. Вопрос: Какой класс в Swift используется для работы с JSON?

    Ответ: JSONDecoder. Он превращает JSON-данные в экземпляры структур, помеченных протоколом Decodable.

  6. Вопрос: Что такое абсолютный и относительный путь к файлу?

    Ответ: Абсолютный путь начинается от корня диска (например, /Users/ivan/file.txt). Относительный — от текущей рабочей папки (например, Documents/file.txt).

  7. Вопрос: Как проверить, существует ли файл по заданному пути, перед тем как его читать?

    Ответ: Использовать FileManager.default.fileExists(atPath:)

  8. Вопрос: Почему не стоит читать файл размером 2 ГБ одной строкой?

    Ответ: Потому что вся эта информация загрузится в оперативную память. Если памяти недостаточно, программа завершится с ошибкой или компьютер зависнет.

  9. Вопрос: Что такое песочница (sandbox) и как она влияет на чтение файлов в iOS-приложениях?

    Ответ: Песочница — это механизм безопасности, который ограничивает доступ приложения к файловой системе. Приложение может читать только свои файлы в специально отведённых папках (Documents, Library, tmp).

  10. Вопрос: Как в Swift прочитать файл из интернета по URL?

    Ответ: Использовать String(contentsOf: url, encoding: .utf8) или Data(contentsOf: url). Но помни: это блокирующая операция, для реальных приложений лучше использовать URLSession.

Итог и что изучить дальше

Мы прошли большой путь. Ты узнал:

  • Что такое файлы и зачем их читать в программах.
  • Как устроено чтение файлов «под капотом»: пути, потоки, кодировки.
  • Все основные способы чтения в Swift: от простого String(contentsOfFile:) до продвинутого FileHandle.
  • Как читать разные форматы: обычный текст, CSV, JSON, бинарные файлы.
  • Как избегать типичных ошибок и писать надёжный код.

Что изучать дальше?

  1. Запись в файлы — теперь, когда ты умеешь читать, научись и писать. Методы write(to:), FileHandle.write().
  2. Работа с папками — создание, удаление, переименование файлов и директорий через FileManager.
  3. Асинхронное чтение — для больших файлов и сетевых запросов используй async/await или URLSession.
  4. Форматы данных — глубже изучи JSON, XML, Property List (plist), YAML.
  5. Базы данных — когда файлов становится много, на помощь приходят SQLite, Core Data, Realm.
🎯 Совет на будущее: Не пытайся выучить всё сразу. Напиши несколько маленьких программ, которые читают разные файлы. Сломай их специально — посмотри, какие ошибки возникают. Почини. Только так приходит настоящее понимание.

Ты сделал первый и самый важный шаг в мир работы с данными. Поздравляю! Теперь ты не просто программист, а программист, который умеет общаться с внешним миром через файлы.