Обновление
Я разрешил и удалил отвлекающую ошибку. Пожалуйста, прочитайте весь пост и не стесняйтесь оставлять комментарии, если остались какие-либо вопросы.
Фон
Я пытаюсь записать относительно большие файлы (видео) на диск на iOS, используя Swift 2.0, GCD и обработчик завершения. Я хотел бы знать, есть ли более эффективный способ выполнения этой задачи. Задача должна быть выполнена без блокировки основного пользовательского интерфейса при использовании логики завершения, а также для обеспечения того, чтобы операция выполнялась как можно быстрее. У меня есть пользовательские объекты с свойством NSData, поэтому я в настоящее время экспериментирую с использованием расширения в NSData. В качестве альтернативы альтернативное решение может включать использование NSFilehandle или NSStreams в сочетании с некоторой формой поточного безопасного поведения, что приводит к гораздо более быстрой пропускной способности, чем функция writeDoURL NSData, на которой я основываю текущее решение.
Что не так с NSData?
Обратите внимание на следующее обсуждение, взятое из ссылки на класс NSData, (Сохранение данных). Я выполняю записи в моем временном каталоге, но главная причина, по которой у меня возникает проблема, заключается в том, что я вижу заметное отставание в пользовательском интерфейсе при работе с большими файлами. Это отставание именно потому, что NSData не является асинхронным (и Apple Docs отмечает, что атомарная запись может вызвать проблемы с производительностью "больших" файлов ~ > 1 мб). Таким образом, при работе с большими файлами каждый находится во власти любого внутреннего механизма, который работает в методах NSData.
Я сделал еще несколько копаний и нашел эту информацию у Apple... "Этот метод идеально подходит для преобразования данных://URL-адреса в объекты NSData, а также может использоваться для синхронного чтения коротких файлов . для чтения потенциально больших файлов, используйте inputStreamWithURL: чтобы открыть поток, затем прочитайте файл за раз." (Ссылка на класс NSData, Objective-C, + dataWithContentsOfURL). Эта информация, по-видимому, подразумевает, что я мог бы попытаться использовать потоки для записи файла на фоновом потоке, если перемещение writeToURL в фоновый поток (как предложено @jtbandes) недостаточно.
Класс NSData и его подклассы предоставляют методы для быстрого и легко сохранить их содержимое на диск. Чтобы свести к минимуму риск потери данных, эти методы обеспечивают возможность сохранения данных атомарно. атомное пишет, что данные либо сохраняются полностью, либо не удается полностью. Атомная запись начинается с записи данных на временный файл. Если эта запись завершается успешно, тогда метод перемещает временный файл в его окончательное местоположение.
В то время как операции атомной записи минимизируют риск потери данных из-за поврежденные или частично написанные файлы, они могут быть неприемлемыми, если запись во временный каталог, домашний каталог пользователей или другие общедоступные каталоги. Каждый раз, когда вы работаете с публично доступный файл, вы должны рассматривать этот файл как ненадежный и потенциально опасный ресурс. Злоумышленник может скомпрометировать или испортить эти файлы. Злоумышленник также может заменить файлы жесткими или символические ссылки, в результате чего ваши операции записи будут перезаписаны или повреждены другие системные ресурсы.
Избегайте использования метода writeToURL: atomically: method (и связанного с ним методы) при работе внутри общедоступного каталога. Вместо инициализировать объект NSFileHandle существующим файловым дескриптором и используйте методы NSFileHandle для надежной записи файла.
Другие альтернативы
Одна статья о параллельном программировании на objc.io предоставляет интересные опции в разделе "Дополнительно: ввод/вывод файлов в фоновом режиме". Некоторые из вариантов включают использование InputStream. Apple также имеет несколько старых ссылок на чтение и запись файлов асинхронно. Я отправляю этот вопрос в ожидании альтернатив Swift.
Пример соответствующего ответа
Вот пример соответствующего ответа, который мог бы удовлетворить этот вопрос. (Взято для руководства по программированию потока, Запись в выходные потоки)
Использование экземпляра NSOutputStream для записи в выходной поток требует нескольких шагов:
- Создайте и инициализируйте экземпляр NSOutputStream с помощью репозиторий для письменных данных. Также установите делегат.
- Расписание потокового объекта в цикле запуска и открыть поток.
- Управление событиями что объект потока сообщает его делегату.
- Если объект потока записывает данные в память, получает данные, запрашивая Свойство NSStreamDataWrittenToMemoryStreamKey.
- Когда больше нет данные для записи, распоряжаться объектом потока.
Я ищу наиболее опытный алгоритм, применимый к написанию чрезвычайно большие файлы для iOS с использованием Swift, API или, возможно, даже C/ObjC было бы достаточно. Я могу перенести алгоритм в соответствующий Swift-совместимые конструкции.
Nota Bene
Я понимаю информационную ошибку ниже. Он включен для полноты.Это вопрос спрашивает, есть ли лучший алгоритм для использования для записи больших файлов на диск с гарантированной последовательностью зависимостей (например, зависимостей NSOperation). Если там есть предоставьте достаточную информацию (описание/образец для меня реконструировать соответствующий Swift 2.0-совместимый код). Пожалуйста, сообщите, если я отсутствует какая-либо информация, которая поможет ответить на вопрос.
Примечание по расширению
Я добавил обработчик завершения в базу writeToURL, чтобы убедиться, что не происходит непреднамеренного совместного использования ресурсов. Мои зависимые задачи, которые используют файл никогда не должны сталкиваться с состоянием гонки.
extension NSData {
func writeToURL(named:String, completion: (result: Bool, url:NSURL?) -> Void) {
let filePath = NSTemporaryDirectory() + named
//var success:Bool = false
let tmpURL = NSURL( fileURLWithPath: filePath )
weak var weakSelf = self
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//write to URL atomically
if weakSelf!.writeToURL(tmpURL, atomically: true) {
if NSFileManager.defaultManager().fileExistsAtPath( filePath ) {
completion(result: true, url:tmpURL)
} else {
completion (result: false, url:tmpURL)
}
}
})
}
}
Этот метод используется для обработки данных пользовательских объектов с контроллера:
var items = [AnyObject]()
if let video = myCustomClass.data {
//video is of type NSData
video.writeToURL("shared.mp4", completion: { (result, url) -> Void in
if result {
items.append(url!)
if items.count > 0 {
let sharedActivityView = UIActivityViewController(activityItems: items, applicationActivities: nil)
self.presentViewController(sharedActivityView, animated: true) { () -> Void in
//finished
}
}
}
})
}
Заключение
Apple Docs on Производительность основных данных дает некоторые полезные советы по работе с давлением памяти и управлению BLOB. Это действительно одна черта статьи с большим количеством подсказок к поведению и как смягчить проблему с большими файлами в вашем приложении. Теперь, хотя это специфично для Core Data, а не файлов, предупреждение об атомной записи говорит мне, что я должен применять методы, которые пишут атомарно с большой осторожностью.
С большими файлами единственным безопасным способом управления записью, кажется, является добавление обработчика завершения (к методу записи) и отображение вида активности в основном потоке. Независимо от того, выполняете ли вы это с потоком или модифицируете существующий API для добавления логики завершения, до читателя. Я сделал оба в прошлом, и я посреди тестирования для лучшей производительности.
До тех пор я меняю решение, чтобы удалить все свойства двоичных данных из Core Data и заменить их на строки для хранения URL-адресов ресурсов на диске. Я также использую встроенные функции из Библиотеки активов и PHAsset для захвата и хранения всех URL-адресов соответствующих активов. Когда мне нужно скопировать какие-либо активы, я буду использовать стандартные методы API (методы экспорта в PHAsset/Asset Library) с обработчиками завершения, чтобы уведомить пользователя о законченном состоянии в основном потоке.
(Действительно полезные фрагменты из статьи Core Data Performance)
Уменьшение накладных расходов памяти
Иногда бывает, что вы хотите использовать управляемые объекты на временную основу, например, для вычисления среднего значения для особый атрибут. Это вызывает граф объекта и память потребление, расти. Вы можете уменьшить накладные расходы памяти на переустановить отдельные управляемые объекты, которые вам больше не нужны, или вы может reset контекст управляемого объекта очистить весь граф объекта. Вы также можете использовать шаблоны, которые применяются к программированию Cocoa в целом.
Вы можете повторно вызвать отдельный управляемый объект, используя NSManagedObjectContexts refreshObject: mergeChanges: метод. Это эффект очистки своих значений свойств в памяти, тем самым уменьшая его памяти. (Обратите внимание, что это не то же самое, что установка значения свойства до nil - значения будут извлекаться по требованию, если ошибка срабатывает - см. "Неисправность и Uniquing".)
Когда вы создаете запрос на выборку, вы можете установить includePropertyValues в NO > для уменьшения издержек памяти, избегая создания объектов для представления значений свойств. Обычно вы должны делать это только в том случае, если вы уверены, что либо вам не понадобятся фактические данные свойств, либо у вас уже есть информация в кеше строк, иначе вы столкнетесь с несколькими поездки в постоянный магазин.
Вы можете использовать метод reset для NSManagedObjectContext для удаления всех управляемых объектов, связанных с контекстом, и "начать сначала", как если бы вы только что создали его. Обратите внимание, что любой управляемый объект, связанный с этим контекстом, будет признан недействительным, поэтому вам нужно будет отбросить любые ссылки и повторно выбрать любые объекты, связанные с этим контекстом, в котором вы все еще заинтересованы. Если вы выполняете итерацию по множеству объектов, вам может потребоваться использование локальных блоков пула автозапуска для обеспечения того, чтобы временные объекты были освобождены как можно скорее.
Если вы не собираетесь использовать функциональность отладки Core Datas, вы можете снизить требования к ресурсам приложений, установив контекст отменить менеджер до нуля. Это может быть особенно полезно для фоновые рабочие потоки, а также для большого импорта или партии операции.
Наконец, Core Data по умолчанию не сохраняет ссылки на управляемые объекты (если у них нет несохраненных изменений). Если у вас много объектов в памяти, вы должны определить принадлежность Рекомендации. Управляемые объекты поддерживают сильные ссылки друг на друга через отношения, которые могут легко создать сильную ссылку циклы. Вы можете разбивать циклы путем повторного сбоя объектов (снова используя refreshObject: mergeChanges: метод NSManagedObjectContext).
Крупные объекты данных (BLOB)
Если ваше приложение использует большие BLOB ( "Binary Large OBjects", такие как изображения и звука), вам необходимо позаботиться о минимизации накладных расходов. Точное определение "малого", "скромного" и "большого" является текучим и зависит от использования приложений. Свободное эмпирическое правило состоит в том, что объекты размером порядка килобайта имеют "скромные" размеры и размеры в мегабайтах размером "большие". Некоторые разработчики достигли хорошей производительности с 10 МБ BLOB в база данных. С другой стороны, если приложение имеет миллионы строк в таблица, даже 128 байтов может быть CLOS с умеренным размером (Character Large OBject), который необходимо нормализовать в отдельной таблице.
В общем случае, если вам нужно хранить BLOB в постоянном хранилище, вы следует использовать хранилище SQLite. Хранилища XML и двоичные файлы требуют, чтобы весь граф объекта находится в памяти, а записи хранятся атомарно (см. Persistent Store Features), что означает, что они неэффективно иметь дело с большими объектами данных. SQLite может масштабироваться для обработки большие базы данных. Правильно используемое SQLite обеспечивает хорошую производительность для базы данных до 100 ГБ, а одна строка может содержать до 1 ГБ (хотя конечно, чтение 1 ГБ данных в память - дорогостоящая операция нет насколько эффективен репозиторий).
BLOB часто представляет собой атрибут объекта - например, фотография может быть атрибутом объекта Employee. Для малых BLOB-объекты с небольшим размером (и CLOB), вы должны создать отдельный объект для данных и создать одну-единственную связь вместо атрибут. Например, вы можете создать сотрудника и фотографию объекты с взаимно-однозначным отношением между ними, где отношения от Employee to Photograph заменяют фотография атрибут. Этот шаблон максимизирует преимущества объекта (см. "Неисправность и Uniquing" ). Любая фотография извлекается, если это действительно необходимо (если связь проходит).
Лучше, однако, если вы можете хранить BLOB в качестве ресурсов на файловой системы и поддерживать ссылки (например, URL-адреса или пути) к тем Ресурсы. Затем вы можете загрузить BLOB и при необходимости.
Примечание:
Я переместил логику ниже в обработчик завершения (см. код выше), и я больше не вижу ошибок. Как упоминалось ранее вопрос о том, есть ли более эффективный способ обрабатывать большие файлы в iOS с помощью Swift.
При попытке обработать массив результирующих элементов для перехода к UIActvityViewController, используя следующую логику:
, если items.count > 0 {
let sharedActivityView = UIActivityViewController (activityItems: items, applicationActivities: nil)
self.presentViewController(sharedActivityView, animated: true) {() → Пустота в
// законченный}
}Дел >
Я вижу следующую ошибку: Ошибка связи: {count = 1,
contents = "XPCErrorDescription" = > {length =
22, contents = "Connection interrupted" }} > (обратите внимание, что я ищу лучший дизайн, а не ответ на это сообщение об ошибке)
Дел >