Как оптимизировать хранение NSAttributedString в Swift, используя данные и кодируемые? - PullRequest
0 голосов
/ 24 ноября 2018

Я пытаюсь оптимизировать пространство хранения при сохранении содержимого NSTextView, а именно его свойства NSTextStorage, само по себе NSAttributedString.

Сохранение его как Data, например, с использованиемrtfd(from:documentAttributes:) метод, и как часть структуры Codable, приводит к очень большой строке, намного большей, чем сам контент, особенно при вставке изображения в NSTextView.Например, вставка изображения размером 200 КБ приведет к созданию файла JSON размером 5 МБ.

Примечание: еще хуже, когда объект Data кодируется непосредственно, а не как свойство закодированного объекта, как этокодируется в виде массива маленьких целых чисел, а не произвольной строки.Я не уверен, почему, хотя я смог предотвратить это, вставив Data в простую структуру оболочки.

Как ни странно, сжатие фактического файла JSON с использованием ZIP по-прежнему приводит к файлу размером 4 МБ, всего 20%, поэтому мне неясно, как изображение размером 200 КБ может превратиться в такую ​​массивную, трудно сжимаемую кодированную строку.

Я хотел бы выяснить, как правильно хранить NSAttributedString, используяCodable протокол.Любая подсказка или совет приветствуются.

Мне также интересно, есть ли допустимая опция двоичного кодирования для Codable.

1 Ответ

0 голосов
/ 13 февраля 2019

TL; DR: RTFD кодирует изображения в формате PNG, но вместо этого вы можете сделать так, чтобы они кодировали JPG, чтобы сэкономить место.Пользовательский формат может быть лучше и проще, хотя, если у вас есть время на его создание.

NSAttributedString может кодировать в HTML, RTF, RTFD, обычный текст, различные форматы Office / Word,и т.д. Учитывая, что каждый из них является официальным форматом с официальной спецификацией, которой необходимо следовать, мало что можно сделать с точки зрения экономии места, кроме:

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

ИЛИ

Написание собственного формата.

Подход 1: RTFD

Из поддерживаемого формата RTFD действительно лучше всего подходит для вашего случая использования, поскольку включает поддержку вложений, таких как изображения,Не стесняйтесь опробовать другие включенные форматы, описания которых приведены ниже в разделе «Другие форматы».

Сохранение в виде данных, например, с использованием метода rtfd (from: documentAttributes :) и как частьCodable структуры, приводит к очень большой строке, намного больше, чем сам контент, особенно при вставке изображения в NSTextView.Например, вставка изображения размером 200 КБ приведет к получению файла JSON размером 5 МБ.

Чтобы понять, что здесь происходит, попробуйте следующий код:

do {
    let rtfd = try someAttributedString.rtfdFileWrapper(from: NSRange(location: 0, length: someAttributedString.length), documentAttributes: [:])
    rtfd?.write(to: URL(fileURLWithPath: "/Users/yourname/someFolder/RTFD.rtfd"), options: .atomic, originalContentsURL: nil)
} catch {
    print("\(error)")
}

Когда вы звоните rtfd(from:documentAttributes:), вы получаете квартиру Data.Эти плоские данные затем могут быть где-то закодированы и считаны обратно в NSAttributedString.Но не заблуждайтесь: RTFD - это формат пакета («D» обозначает каталог).Таким образом, вместо вызова rtfdFileWrapper(from:documentAttributes:) и записи этого в URL с расширением rtfd, мы можем увидеть фактический формат пакета, который rtfd(from:documentAttributes:) реплицирует, но как каталог вместо необработанных данных.В Finder щелкните правой кнопкой мыши созданный файл и выберите «Показать содержимое пакета».

В пакете RTFD содержится файл RTF для указания текста и атрибутов, а также копия каждого вложения.Так почему же ваш пример был намного больше?В моих тестах ответ, кажется, заключается в том, что RTFD ожидает найти свои изображения в формате PNG.При вызове rtfdFileWrapper(from:documentAttributes:) или rtfd(from:documentAttributes:) любые вложения изображений выглядят записанными в виде файлов PNG, которые занимают значительно больше места.Это происходит потому, что ваше изображение обернуто в NSImage до того, как оно будет обернуто в NSTextAttachment.NSImage может записывать данные изображения в других форматах, включая большие форматы, такие как PNG.

Я предполагаю, что изображение, которое вы пробовали, было в сжатом формате, таком как JPEG, и NSAttributedString записало егов RTFD как PNG.

Использование JPEG вместо

Предполагая, что вы в порядке со сжатым изображением и не имеете информации, такой как альфа-канал, вам следуетбыть в состоянии создать файл RTFD с jpg изображениями.

Например, мне удалось получить файл RTFD до 2,8 МБ с более чем 12 МБ (большое изображение), просто заменив сгенерированное изображение PNG наоригинальный JPG один.Первоначально это было неприемлемо для TextEdit, но затем я изменил расширение файла изображения на .png (даже если это все еще JPG), и он принял его.

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

// Don't do this unless you want PNG
let image = NSImage(contentsOf: ...) // NSImage will write to a larger PNG file
let attachment = NSTextAttachment()
attachment.image = image

// Do this if you want smaller files
let image = try? Data(contentsOf: ...) // This will remain in raw JPG format
let attachment = NSTextAttachment(data: image, ofType: kUTTypeJPEG as String) // Explicitly specify JPG

Затем, когда вы создадите новый NSAttributedString с этим NSTextAttachment и добавите его к NSTextStorage, записывая данные RTFDбудет значительно меньше.

Конечно, вы не можете контролировать этот процесс, если полагаетесь на пользовательский интерфейс / API Какао для прикрепления изображений.Это может усложнить процесс, и вам, возможно, придется прибегнуть к изменению сгенерированных данных путем замены изображений.

Подход 2. Пользовательский формат

Описанный выше подход может быть неудобным из-за отсутствияиметь контроль над процессом добавления вложений и требовать плоских данных.В этом случае пользовательский формат может быть лучше.

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

Мне также интересно, есть ли допустимая опция двоичного кодирования для Codable.

Во-первых, обратите внимание, что NSAttributedString является классом Objective-C (при использовании на платформах Apple) и соответствует NSSecureCoding вместо Codable.

Обратите внимание, чтоВы не можете расширить NSAttributedString, чтобы соответствовать Codable, потому что требование init(from:) для Decodable может быть выполнено только при условии, что инициализатор будет включен также во все подклассы.Поскольку этот класс не final, это означает, что он может быть удовлетворен только required init.Обязательные инициализаторы могут быть указаны только в исходном объявлении, а не в расширениях.

По этой причине, если вы хотите согласовать его с Codable, вам нужно будет использовать объект-оболочку.enumerateAttributes(in:options:using:) должно быть полезно для получения атрибутов и необработанных символов, которые необходимо кодировать, но вы должны быть уверены, что обратите внимание и на изображения.

Что касается кодирования в двоичном коде, Codable совершенно не зависит от формата, поэтому вы можете написать свой собственный объект, соответствующий Coder, который делает все, что вы хотите, в том числе хранить все, используя необработанные байты.

В стороне: другие форматы

Вот краткое изложение других поддерживаемых форматов (в порядке размера).В этих тестах я использовал очень маленькую строку "Hello World! There's so much to see!" в системном шрифте.После каждого описания формата (в скобках) указывается количество байтов для хранения этой строки.

  • Обычный текст может хранить указанный выше формат в 36 байтах (1 для каждого символа),но не будет сохранять атрибуты или вложения.(36 байт)
  • RTF кажется наиболее легковесным, если вам нужно сохранить атрибуты, но не вложения.(331 байт)
  • HTML Следующий самый легкий, но на самом деле не предназначен для формата хранения.По моему опыту, некоторые атрибуты, такие как межстрочный интервал, теряются при конвертации в HTML NSAttributedString.(536 байт)
  • Binary Plist , который создается при использовании NSKeyedArchiver, является хорошим вариантом, если вам нужна только совместимость с платформами Apple и вам не нравятся указанные выше форматы.Этот формат также поддерживает изображения, но, как правило, по-прежнему больше, чем указано выше (и RTFD).(648 байт)
  • Веб-архив следующий по размеру, но я не рекомендую использовать его, поскольку WebKit его устарел.Safari все еще использует его для некоторых вещей.(784 байта)
  • Слово ML , вероятно, полезно только для тех, кто уже знает, что им это нужно.Этот формат и все, что находится под ним, как правило, будет содержать набор шаблонов, которые будут становиться меньшим процентным содержанием файла по мере добавления текста.(~ 1,2 МБ)
  • Открытый документ (OASIS) меньше, чем большинство форматов Word, но вы, вероятно, не использовали бы его без веской причины.(~ 2,4 МБ)
  • Office Open XML Это другой формат, который вы используете, только если вам нужен именно этот формат.(~ 3,5 МБ)
  • Документ (Microsoft Word) Этот файл очень большой по сравнению с небольшими объемами текста.В то время как я ожидал, что этот формат разрешает изображения, в моем тестировании размер файла фактически не увеличивался, когда я добавлял один.(~ 19,4 МБ)
  • Mac Simple Text , кажется, всегда генерирует ошибку.(N / A)

Заключительное примечание

В конце концов, опыт кодирования для NSAttributedString должен улучшиться, поскольку Foundation продолжает адаптироваться к Swift, а не к Objective-C.Вы можете представить себе день, когда NSAttributedString или какой-либо аналогичный тип Swifty соответствует Codable из коробки и затем может быть связан с любым форматом файла Coder.

...