Как записать словарь в файл?

У меня есть класс FileHelper, в котором я реализовал 3 метода, задачей которых является запись содержимого словаря в файл. Эти методы:

func storeDictionary(_ dictionary: Dictionary<String, String>, inFile fileName: String, atDirectory directory: String) -> Bool {
    let ext = "txt"
    let filePath = createFile(fileName, withExtension: ext, atDirectory: directory)
    /**** //If I use this method, file is created and dictionary is saved
    guard (dictionary as NSDictionary).write(to: filePath!, atomically: true) else {
        return false
    }
    */
    guard NSKeyedArchiver.archiveRootObject(dictionary, toFile: (filePath?.absoluteString)!) else {
        return false
    }
    return true
}
func createFile(_ file: String, withExtension ext: String, atDirectory directory: String) -> URL? {
    let directoryPath = createDirectory(directory)
    let filePath = directoryPath?.appendingPathComponent(file).appendingPathExtension(ext)

    if !FileManager.default.fileExists(atPath: (filePath?.absoluteString)!) {
        let success = FileManager.default.createFile(atPath: (filePath?.absoluteString)!, contents: nil, attributes: nil)
        print("\(success)") //** here is the issue I investigated. Always prints false.
    }

    return filePath
}
func createDirectory(_ directory: String) -> URL? {
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let directoryPath = documentsDirectory.appendingPathComponent(directory)

    do {
        try FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: true, attributes: nil)
    } catch let error as NSError {
        fatalError("Error creating directory: \(error.localizedDescription)")
    }
    return directoryPath
}

Когда я вызываю FileHelper().storeDictionary(aValidDictionary, inFile: "abc", atDirectory: "XYZ") для записи словаря, эта процедура завершается ошибкой. Но если я использую

guard (dictionary as NSDictionary).write(to: filePath!, atomically: true) else {
    return false
}

оно работает.

Что не так с методом NSKeyedArchiver.archiveRootObject(_:toFile:)??

И почему FileManager.default.createFile(atPath: (filePath?.absoluteString)!, contents: nil, attributes: nil) всегда возвращает false?


person nayem    schedule 15.03.2017    source источник


Ответы (2)


Во-первых, filePath?.absoluteString возвращает всю строку (даже процент экранирования), включая схему file://, а метод ожидает путь без схемы (filePath?.path — название немного сбивает с толку ;-) ).

Я рекомендую сохранить словарь [String:String] как файл со списком свойств. Нет необходимости создавать файл явно.

Я немного изменил сигнатуры методов в Swift-3-way. Далее нет необходимости использовать какой-либо необязательный тип.

func store(dictionary: Dictionary<String, String>, in fileName: String, at directory: String) -> Bool {
    let fileExtension = "plist"
    let directoryURL = create(directory:directory)
    do {
        let data = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
        try data.write(to: directoryURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension))
        return true
    }  catch {
        print(error)
        return false
    }
}

func create(directory: String) -> URL {
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let directoryURL = documentsDirectory.appendingPathComponent(directory)

    do {
        try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
    } catch let error as NSError {
        fatalError("Error creating directory: \(error.localizedDescription)")
    }
    return directoryURL
}

PS: вместо того, чтобы возвращать Bool, вы можете сделать так, чтобы метод хранилища can throw и обрабатывал ошибку в вызывающем методе:

func store(dictionary: Dictionary<String, String>, in fileName: String, at directory: String) throws {
    let fileExtension = "plist"
    let directoryURL = create(directory:directory)

    let data = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
    try data.write(to: directoryURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension))
}
person vadian    schedule 15.03.2017
comment
Ну, я понимаю. Но что вы имели в виду под filePath?.path - название немного сбивает с толку ;-), я не понял - person nayem; 15.03.2017
comment
Ваш filePath на самом деле fileURL, экземпляр (NS)URL. path подразумевает (NS)String. - person vadian; 15.03.2017
comment
О, мой плохой. Быть глупым ????. Еще одна вещь, которую я хочу знать, это удаление _ из сигнатуры метода Swift-3 способом? Тогда когда использовать _s? - person nayem; 15.03.2017
comment
Вы должны передать _, если хотите игнорировать соответствующую (внешнюю) метку параметра. Однако соглашение Swift 3 заключается в передаче всех меток параметров. Иногда они игнорируются по причинам совместимости с Objective-C. - person vadian; 15.03.2017
comment
Рад узнать, что это (_) в основном используется для людей, которые пришли из Objective-C, чтобы они чувствовали себя как соглашения Objective-C. Верно? - person nayem; 15.03.2017
comment
Вроде, как бы, что-то вроде. Но это не основная цель. - person vadian; 15.03.2017
comment
Одна проблема. FileManager.default.fileExists(atPath: fileURL.absoluteString) или FileManager.default.fileExists(atPath: fileURL.path) всегда возвращает false. Я думаю, что сигнатура метода fileExists(atPath:) означает fileDoesNotExist(atPath:). Обратный способ. Можешь взглянуть? - person nayem; 20.03.2017
comment
fileExists(atPath возвращает true, если файл существует. Но, используя мое предложение, вам не нужно явно создавать файл. Еще раз: никогда не используйте absoluteString в локальной файловой системе. - person vadian; 20.03.2017
comment
Да. Эта проблема была решена. Но я столкнулся с новой проблемой. То есть я пытался загрузить свои данные и распечатать их. По этой причине я попробовал метод fileExists(atPath:). Но похоже, что метод всегда возвращает false, независимо от того, существует ли файл в этом месте на самом деле или нет. Я думаю, что это ошибка в Swift 3 - person nayem; 20.03.2017
comment
Это точно не баг. Учтите, что абсолютные пути указывают на разные местоположения в изолированных и не изолированных приложениях. - person vadian; 20.03.2017

Вот расширение Swift 5, которое должно сохранять любой словарь, где Key и Value равны Codable

extension Dictionary where Key: Codable, Value: Codable {
    static func load(fromFileName fileName: String, using fileManager: FileManager = .default) -> [Key: Value]? {
        let fileURL = Self.getDocumentsURL(on: .cachesDirectory, withName: fileName, using: fileManager)
        guard let data = fileManager.contents(atPath: fileURL.path) else { return nil }
        do {
            return try JSONDecoder().decode([Key: Value].self, from: data)
        } catch(let error) {
            print(error)
            return nil
        }
    }

    func saveToDisk(on directory: FileManager.SearchPathDirectory,
                    withName name: String,
                    using fileManager: FileManager = .default) throws {

        let fileURL = Self.getDocumentsURL(on: .cachesDirectory, withName: name, using: fileManager)
        let data = try JSONEncoder().encode(self)
        try data.write(to: fileURL)
    }

    private static func getDocumentsURL(on directory: FileManager.SearchPathDirectory,
                                 withName name: String,
                                 using fileManager: FileManager) -> URL {

        let folderURLs = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
        let fileURL = folderURLs[0].appendingPathComponent(name)
        return fileURL
    }
}

Применение:

let myDict = [MyKey: MyValue].load(from: diskDirectory, andFileName: diskFileName) // load
try myDict.saveToDisk(on: diskDirectory, withName: diskFileName) // save
person Declan McKenna    schedule 26.02.2021