Avatar ·

Чтение и запись строки в текстовый файл на Swift

Всем привет! Я новичок в Swift и пытаюсь разобраться с чтением и записью строк в текстовый файл. Перерыл кучу документации, но всё равно путаюсь. Мне нужно сохранить небольшой текст (например, логи или настройки) в файл, а потом прочитать его обратно. Я пробовал писать что-то вроде: ```swift let myString = "Hello, World!" let fileURL = URL(fileURLWithPath: "myFile.txt") try myString.write(to: fileURL, atomically: true, encoding: .utf8) ``` Но Xcode ругается на `try` без обработки ошибок. Когда я добавляю `do-catch` или `try!`, файл то ли создаётся, то ли нет — я не могу найти его на диске. С чтением та же беда: пишу `String(contentsOf: fileURL)`, а получаю краш с ошибкой "The file doesn’t exist". Что я делаю не так? Как правильно указать путь к файлу? Нужно ли создавать папки вручную? И как обрабатывать ошибки, чтобы приложение не падало? Объясните, пожалуйста, на простых примерах, как работать с файлами в Swift — от начала до конца. Заранее спасибо! <<>>

Понимание путей и файловой системы

Главная проблема в вашем коде — вы используете относительный путь "myFile.txt". На iOS/macOS приложения имеют изолированную файловую систему. Если не указать полный путь, файл создаётся в текущей рабочей директории, которая часто недоступна или временна. Всегда используйте системные директории, такие как Documents или cachesDirectory.

Правильный способ записи строки в файл

Вот рабочий пример с обработкой ошибок:

import Foundation

func saveStringToFile(_ text: String, fileName: String) {
    // Получаем путь к папке Documents
    guard let documentsDirectory = FileManager.default.urls(
        for: .documentDirectory, 
        in: .userDomainMask
    ).first else {
        print("Не удалось получить папку Documents")
        return
    }
    
    // Создаём полный путь к файлу
    let fileURL = documentsDirectory.appendingPathComponent(fileName)
    
    do {
        try text.write(to: fileURL, atomically: true, encoding: .utf8)
        print("Файл сохранён: \(fileURL.path)")
    } catch {
        print("Ошибка записи: \(error.localizedDescription)")
    }
}

// Использование:
saveStringToFile("Привет, Мир!", fileName: "myFile.txt")

Чтение строки из файла

func readStringFromFile(fileName: String) -> String? {
    guard let documentsDirectory = FileManager.default.urls(
        for: .documentDirectory, 
        in: .userDomainMask
    ).first else {
        print("Не удалось получить папку Documents")
        return nil
    }
    
    let fileURL = documentsDirectory.appendingPathComponent(fileName)
    
    // Проверяем, существует ли файл
    guard FileManager.default.fileExists(atPath: fileURL.path) else {
        print("Файл не найден: \(fileURL.path)")
        return nil
    }
    
    do {
        let text = try String(contentsOf: fileURL, encoding: .utf8)
        return text
    } catch {
        print("Ошибка чтения: \(error.localizedDescription)")
        return nil
    }
}

// Использование:
if let content = readStringFromFile(fileName: "myFile.txt") {
    print("Содержимое: \(content)")
}

Ключевые моменты

  • FileManager.default.urls(for:in:) — получает системные директории. Всегда используйте его.
  • appendingPathComponent — безопасно добавляет имя файла к пути.
  • atomically: true — записывает во временный файл, затем переименовывает. Это предотвращает повреждение данных при сбое.
  • fileExists(atPath:) — проверяет существование файла перед чтением.
  • Обработка ошибок — всегда используйте do-catch или возвращайте опциональное значение.

Дополнительные советы

Если вам нужно создать вложенные папки, используйте:

try FileManager.default.createDirectory(
    at: fileURL.deletingLastPathComponent(),
    withIntermediateDirectories: true
)

Для временных файлов используйте FileManager.default.temporaryDirectory. На macOS вы можете проверить созданные файлы через Finder в папке Documents вашего приложения, а на iOS — через симулятор (путь покажется в консоли после print(fileURL.path)).

<<>>

Использование FileManager для полного контроля

Первый ответ хорош, но иногда нужно больше гибкости — например, перезаписать файл или добавить данные в конец. Вот альтернативный подход с FileHandle:

func appendString(_ text: String, to fileName: String) {
    guard let documentsDirectory = FileManager.default.urls(
        for: .documentDirectory, 
        in: .userDomainMask
    ).first else { return }
    
    let fileURL = documentsDirectory.appendingPathComponent(fileName)
    
    // Создаём файл, если его нет
    if !FileManager.default.fileExists(atPath: fileURL.path) {
        FileManager.default.createFile(atPath: fileURL.path, contents: nil)
    }
    
    do {
        let fileHandle = try FileHandle(forWritingTo: fileURL)
        fileHandle.seekToEndOfFile()
        if let data = text.data(using: .utf8) {
            fileHandle.write(data)
        }
        fileHandle.closeFile()
    } catch {
        print("Ошибка: \(error)")
    }
}

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