NSCoding AND Codable Properties ‹=› Формат JSON ‹=› (Чтение/Запись) Файл

Мне нужно читать/записывать свойства, которые являются Codable (например, Date) и NSCoding (например, NSMutableAttributedString) из/в файл в формате JSON. Изучив, как читать и записывать файлы с помощью Codable, как делать это в формате JSON и как комбинировать NSCoding с Codable, когда некоторые свойства не соответствуют Codable (но соответствуют NSCoding), я собрался вместе следующий код и запутался в процессе.

Наконец-то я понял, как это проверить, и внес соответствующие изменения. Но мне все же хотелось бы знать, как три типа декодера/кодировщика (NSCoding, Codable и JSON) взаимодействуют или заменяют друг друга.

import Foundation

class Content: Codable {

    // Content
    var attrStr = NSMutableAttributedString(string: "")
    var date: Date?

    // Initializer for content
    init(attrStr: NSMutableAttributedString, date: Date) {
        self.attrStr = attrStr
        self.date = date
}

    // Need to explicitly define because NSMutableAttributedString isn't codable
    enum CodingKeys: String, CodingKey {

        case attrStr
        case date
    }

    // Need to explicitly define the decoder. . . .
    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        date = try container.decode(Date.self, forKey: .date)

        let attrStrData = try container.decode(Data.self, forKey: .attrStr)
        attrStr = NSKeyedUnarchiver.unarchiveObject(with: attrStrData) as? NSMutableAttributedString ?? NSMutableAttributedString(string: "Error!")
    }

    // Need to explicitly define the encoder. . . .
    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(date, forKey: .date)

        let attrStrData = NSKeyedArchiver.archivedData(withRootObject: attrStr)
        try container.encode(attrStrData, forKey: .attrStr)
    }

    static func getFileURL() -> URL {

        // Get the directory for the file
        let docsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        // Get the full path and filename
        return docsDir.appendingPathComponent("contentArray").appendingPathExtension("cntnt")
    }

    static func saveToFile(content: [Content]) {

        // Get the file's URL
        let fileURL = getFileURL()

        do {
            // Encode the data
            let data = try JSONEncoder().encode(content)
            // Write to a/the file
            try data.write(to: fileURL)

        } catch {
            print("Could not encode or save to the file!")
        }
    }

    static func loadFromFile() -> [Content] {

        // Get the file's URL
        let fileURL = getFileURL()

        do {
            // Read from the file
            let data = try Data(contentsOf: fileURL)
            // Decode the data
            return try JSONDecoder().decode([Content].self, from: data)

        } catch {
            print("Could not decode or read from the file!")
            return []
        }
    }
}

person Optimalist    schedule 18.08.2018    source источник
comment
Это работает??? Альтернативой является реализация Codable для NSMutableAttributedString, которая может выглядеть лучше, но не должна меняться, работает она или нет.   -  person Fabian    schedule 19.08.2018
comment
@Purpose, я еще не придумал, как бы это проверить. Я все еще новичок в ООП, я не программист, и кодирую только время от времени из-за нехватки времени. Но это тоже выглядит неправильно. Например, явно определенные декодер и кодировщик кажутся отсоединенными от JSONDecoder и -Encoder. Должен быть заметный поток обработки, но я не вижу, как три типа декодеров/кодировщиков взаимодействуют или заменяют друг друга. Меня даже смущает декодер init: учитывая его наличие, как мне написать инициализатор для моих свойств? Что касается вашего варианта, я бы не знал, как это сделать.   -  person Optimalist    schedule 19.08.2018
comment
Часть NSCoding может работать, только если класс наследуется от NSObject и принимает NSCoding   -  person vadian    schedule 19.08.2018
comment
@vadian, тогда почему мой код теперь работает (как проверено на игровой площадке)? NSKeyedArchiver и NSKeyedUnarchiver наследуются от NSCoder. Теперь, помимо всего прочего, я запутался в разнице между NSCoder и NSCoding. Возможно, приведенный выше код работает, потому что я не использую NSCoding.   -  person Optimalist    schedule 19.08.2018
comment
Возможно, игровые площадки ведут себя по-другому. Думаю, в обычном проекте вы получите ошибки компилятора.   -  person vadian    schedule 19.08.2018
comment
@vadian, я только что интегрировал (более сложный вариант) приведенный выше код в свое приложение, и в симуляторе он заработал без ошибок. Использование кода, который я на самом деле не понимаю, не очень приятно, но определенно приятно, что у меня есть что-то, что делает то, что мне нужно.   -  person Optimalist    schedule 19.08.2018


Ответы (1)


Что касается вашего варианта, я бы не знал, как это сделать.

Я попробовал реализовать Codable для NSMutableAttributedString. Мне пришлось встроить вместо того, чтобы создавать подклассы, поскольку это кластер классов. Источник

class MutableAttributedStringContainer: Codable {
    let attributedString: NSMutableAttributedString

    init(attributedString: NSMutableAttributedString) {
        self.attributedString = attributedString
    }

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let data = try container.decode(Data.self)

        let archiver = try NSKeyedUnarchiver(forReadingFrom: data)
        attributedString = NSMutableAttributedString(coder: archiver)!
    }

    public func encode(to encoder: Encoder) throws {
        let archiver = NSKeyedArchiver(requiringSecureCoding: true)
        attributedString.encode(with: archiver)

        var container = encoder.singleValueContainer()
        try container.encode(archiver.encodedData)
    }
}

Вот пример того, как его использовать.

func testing() {
    let attributedString = NSMutableAttributedString(string: "Hello world!")
    let attributedStringContainer = MutableAttributedStringContainer(attributedString: attributedString)

    // Necessary because encoding into a singleValueContainer() creates a
    // JSON fragment instead of a JSON dictionary that `JSONEncoder` wants
    // create.
    struct Welcome: Codable {
        var attributedString: MutableAttributedStringContainer
    }
    let welcome = Welcome(attributedString: attributedStringContainer)

    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    let data = try! encoder.encode(welcome)
    print(String(bytes: data, encoding: .utf8) as Any)

    let decoder = JSONDecoder()
    let welcome2 = try! decoder.decode(Welcome.self, from: data)
    print("decoded to string:", welcome2.attributedString.attributedString.string)
}

Но это тоже выглядит неправильно. Например, явно определенные декодер и кодировщик кажутся отсоединенными от JSONDecoder и -Encoder.

Codable структуры строятся друг на друге. Если все базовые структуры реализуют Codable, компилятор может сам создавать функции кодирования и декодирования. Если нет, разработчик должен закодировать их и поставить на CodingKey, то же самое для декодирования.

Например, их можно преобразовать в данные любым способом, а затем закодировать как данные в CodingKey. Возможно, прочитайте учебник Raywenderlich на Codable, чтобы понять это лучше.

Должен быть заметный поток обработки, но я не вижу, как три типа декодеров/кодировщиков взаимодействуют или заменяют друг друга.

Существуют декодеры/кодировщики и методы, которые поддерживают конкретную пару кодировщик/декодер.

NSCoding работает вместе с NSKeyedUnarchiver/NSKeyedArchiver и возвращает NSData, которые являются просто данными, хотя и не в удобочитаемой форме.

Codable работает вместе с любой парой кодировщик/декодер, которая поддерживает Codable, в частности, в нашем случае JSONEncoder/JSONDecoder, которая возвращает Data в удобочитаемом формате JSON и может быть напечатана, поскольку данные здесь закодированы в .utf8.

person Fabian    schedule 19.08.2018
comment
Спасибо за очень подробное объяснение! Но поскольку мой обновленный код теперь работает, я не думаю, что буду использовать вашу альтернативу. (Изначально вы предположили, что реализация Codable для NSMutableAttributedString будет выглядеть лучше, но, похоже, это требует написания намного большего количества кода, не так ли? Это дает более читаемый результат?) В любом случае, я уже читал Учебник Raywenderlich, но мне все еще было неясно. Я все еще думаю в терминах процедурного кода, поэтому мне трудно понять, когда вызываются части De-/Encoder и JSONDe-/JSONEncoder и по какому механизму. - person Optimalist; 19.08.2018
comment
Из ваших замечаний кажется, что я использую пользовательские реализации Codable вместе с JSONEncoder/JSONDecoder именно так, как это делается. Поскольку оба используют кодировщик и декодер, я не был уверен, должен ли тип JSON заменить или дополнить обычный тип Codable. (Возможно, я неправильно использую жаргон.) Думаю, последнее. И мое использование NSKeyedUnarchiver/NSKeyedArchiver просто преобразовало мою NSMutableAttributedString в форму, с которой может работать Codable. - person Optimalist; 19.08.2018