Подтвердить что ты не робот

Как эффективно писать большие файлы на диск в фоновом потоке (Swift)

Обновление

Я разрешил и удалил отвлекающую ошибку. Пожалуйста, прочитайте весь пост и не стесняйтесь оставлять комментарии, если остались какие-либо вопросы.

Фон

Я пытаюсь записать относительно большие файлы (видео) на диск на 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" }} > (обратите внимание, что я ищу лучший дизайн, а не ответ на это сообщение об ошибке) Дел >

4b9b3361

Ответ 1

Производительность зависит от того, какие данные находятся в ОЗУ. Если это так, то вы должны использовать NSData writeToURL с включенной функцией atomically, что вы делаете.

Apple отмечает, что это опасно, когда "запись в общий каталог" совершенно не имеет отношения к iOS, потому что нет общедоступных каталогов. Этот раздел применим только к OS X. И, откровенно говоря, это тоже не так важно.

Итак, код, который вы написали, настолько эффективен, насколько это возможно, если видео помещается в ОЗУ (около 100 МБ будет безопасным лимитом).

Для файлов, которые не подходят в ОЗУ, вам необходимо использовать поток, или ваше приложение выйдет из строя, удерживая видео в памяти. Чтобы загрузить большое видео с сервера и записать его на диск, вы должны использовать NSURLSessionDownloadTask.

В общем, потоковая передача (включая NSURLSessionDownloadTask) будет на порядок медленнее, чем NSData.writeToURL(). Поэтому не используйте поток, если вам это нужно. Все операции на NSData чрезвычайно быстрые, он отлично справляется с файлами размером в несколько терабайт с отличной производительностью на OS X (у iOS, очевидно, не может быть файлов, больших, но это тот же класс с одинаковой производительностью).


В коде есть несколько проблем.

Это неправильно:

let filePath = NSTemporaryDirectory() + named

Вместо этого всегда:

let filePath = NSTemporaryDirectory().stringByAppendingPathComponent(named)

Но это тоже не идеально, вам следует избегать использования путей (они багги и медленны). Вместо этого используйте такой URL:

let tmpDir = NSURL(fileURLWithPath: NSTemporaryDirectory()) as NSURL!
let fileURL = tmpDir.URLByAppendingPathComponent(named)

Кроме того, вы используете путь для проверки наличия файла... не делайте этого:

if NSFileManager.defaultManager().fileExistsAtPath( filePath ) {

Вместо этого используйте NSURL, чтобы проверить, существует ли он:

if fileURL.checkResourceIsReachableAndReturnError(nil) {

Ответ 2

Последнее решение (2018)

Другая полезная возможность может включать использование замыкания всякий раз, когда буфер заполнен (или, если вы использовали длительность записи по времени), чтобы добавить данные, а также объявить об окончании потока данных. В сочетании с некоторыми API Photo это может привести к хорошим результатам. Поэтому некоторый декларативный код, подобный приведенному ниже, может быть запущен во время обработки:

var dataSpoolingFinished: ((URL?, Error?) -> Void)?
var dataSpooling: ((Data?, Error?) -> Void)?

Обработка этих замыканий в вашем объекте управления может позволить вам кратко обрабатывать данные любого размера, сохраняя при этом память под контролем.

Объедините эту идею с использованием рекурсивного метода, который объединяет фрагменты работы в одну группу dispatch_group, и могут быть некоторые захватывающие возможности.

Документы Apple заявляют:

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

Другие заслуживающие внимания решения (~ 2016)

Я не сомневаюсь, что уточню это еще немного, но тема достаточно сложна, чтобы оправдать отдельный ответ. Я решил воспользоваться некоторыми советами из других ответов и использовать подклассы NSStream. Это решение основано на образце Obj-C (пример NSInputStream inputStreamWithURL ios, 2013, 12 мая), размещенном в блоге SampleCodeBank.

В документации Apple отмечается, что с подклассом NSStream вам НЕ нужно загружать все данные в память одновременно. Это ключ к возможности управлять мультимедийными файлами любого размера (не превышающего доступного места на диске или в ОЗУ).

NSStream - это абстрактный класс для объектов, представляющих потоки. Его интерфейс является общим для всех потоковых классов Cocoa, включая его конкретные подклассы NSInputStream и NSOutputStream.

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

Руководство по программированию файловой системы

Apple, обрабатывающая весь файл линейно с помощью потоков в статье FSPG, также предоставила идею о том, что NSInputStream и NSOutputStream должны быть поточно- NSOutputStream.

file-processing-with-streams

Дальнейшие уточнения

Этот объект не использует методы делегирования потока. Много места и для других доработок, но это основной подход, который я выберу. Основное внимание на iPhone уделяется управлению большими файлами при ограничении памяти через буфер (TBD - использование буфера outputStream в памяти). Чтобы было ясно, Apple упоминает, что их удобные функции writeToURL предназначены только для файлов меньшего размера (но меня удивляет, почему они не заботятся о файлах большего размера - обратите внимание, что это не крайние случаи), которые будут задавать вопрос как ошибку).

Заключение

Мне нужно будет дополнительно протестировать интеграцию в фоновом потоке, так как я не хочу вмешиваться ни в NSStream внутреннюю очередь NSStream. У меня есть некоторые другие объекты, которые используют аналогичные идеи для управления очень большими файлами данных по проводам. Лучший способ - сохранить как можно меньшие размеры файлов в iOS, чтобы сохранить память и предотвратить сбои приложения. API строятся с учетом этих ограничений (поэтому попытка неограниченного видео не является хорошей идеей), поэтому мне придется адаптировать ожидания в целом.

(Источник Gist, Проверьте Gist для последних изменений)

import Foundation
import Darwin.Mach.mach_time

class MNGStreamReaderWriter:NSObject {

    var copyOutput:NSOutputStream?
    var fileInput:NSInputStream?
    var outputStream:NSOutputStream? = NSOutputStream(toMemory: ())
    var urlInput:NSURL?

    convenience init(srcURL:NSURL, targetURL:NSURL) {
        self.init()
        self.fileInput  = NSInputStream(URL: srcURL)
        self.copyOutput = NSOutputStream(URL: targetURL, append: false)
        self.urlInput   = srcURL

    }

    func copyFileURLToURL(destURL:NSURL, withProgressBlock block: (fileSize:Double,percent:Double,estimatedTimeRemaining:Double) -> ()){

        guard let copyOutput = self.copyOutput, let fileInput = self.fileInput, let urlInput = self.urlInput else { return }

        let fileSize            = sizeOfInputFile(urlInput)
        let bufferSize          = 4096
        let buffer              = UnsafeMutablePointer<UInt8>.alloc(bufferSize)
        var bytesToWrite        = 0
        var bytesWritten        = 0
        var counter             = 0
        var copySize            = 0

        fileInput.open()
        copyOutput.open()

        //start time
        let time0 = mach_absolute_time()

        while fileInput.hasBytesAvailable {

            repeat {

                bytesToWrite    = fileInput.read(buffer, maxLength: bufferSize)
                bytesWritten    = copyOutput.write(buffer, maxLength: bufferSize)

                //check for errors
                if bytesToWrite < 0 {
                    print(fileInput.streamStatus.rawValue)
                }
                if bytesWritten == -1 {
                    print(copyOutput.streamStatus.rawValue)
                }
                //move read pointer to next section
                bytesToWrite -= bytesWritten
                copySize += bytesWritten

            if bytesToWrite > 0 {
                //move block of memory
                memmove(buffer, buffer + bytesWritten, bytesToWrite)
                }

            } while bytesToWrite > 0

            if fileSize != nil && (++counter % 10 == 0) {
                //passback a progress tuple
                let percent     = Double(copySize/fileSize!)
                let time1       = mach_absolute_time()
                let elapsed     = Double (time1 - time0)/Double(NSEC_PER_SEC)
                let estTimeLeft = ((1 - percent) / percent) * elapsed

                block(fileSize: Double(copySize), percent: percent, estimatedTimeRemaining: estTimeLeft)
            }
        }

        //send final progress tuple
        block(fileSize: Double(copySize), percent: 1, estimatedTimeRemaining: 0)


        //close streams
        if fileInput.streamStatus == .AtEnd {
            fileInput.close()

        }
        if copyOutput.streamStatus != .Writing && copyOutput.streamStatus != .Error {
            copyOutput.close()
        }



    }

    func sizeOfInputFile(src:NSURL) -> Int? {

        do {
            let fileSize = try NSFileManager.defaultManager().attributesOfItemAtPath(src.path!)
            return fileSize["fileSize"]  as? Int

        } catch let inputFileError as NSError {
            print(inputFileError.localizedDescription,inputFileError.localizedRecoverySuggestion)
        }

        return nil
    }


}

Делегация

Здесь похожий объект, который я переписал из статьи о расширенных файлах ввода-вывода в фоновом режиме, Eidhof, C., ObjC.io). С помощью всего лишь нескольких настроек это можно сделать, чтобы подражать поведению выше. Просто перенаправьте данные в NSOutputStream в методе processDataChunk.

(Источник Gist - Проверьте Gist для последних изменений)

import Foundation

class MNGStreamReader: NSObject, NSStreamDelegate {

    var callback: ((lineNumber: UInt , stringValue: String) -> ())?
    var completion: ((Int) -> Void)?
    var fileURL:NSURL?
    var inputData:NSData?
    var inputStream: NSInputStream?
    var lineNumber:UInt = 0
    var queue:NSOperationQueue?
    var remainder:NSMutableData?
    var delimiter:NSData?
    //var reader:NSInputStreamReader?

    func enumerateLinesWithBlock(block: (UInt, String)->() , completionHandler completion:(numberOfLines:Int) -> Void ) {

        if self.queue == nil {
            self.queue = NSOperationQueue()
            self.queue!.maxConcurrentOperationCount = 1
        }

        assert(self.queue!.maxConcurrentOperationCount == 1, "Queue can't be concurrent.")
        assert(self.inputStream == nil, "Cannot process multiple input streams in parallel")

        self.callback = block
        self.completion = completion

        if self.fileURL != nil {
            self.inputStream = NSInputStream(URL: self.fileURL!)
        } else if self.inputData != nil {
            self.inputStream = NSInputStream(data: self.inputData!)
        }

        self.inputStream!.delegate = self
        self.inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
        self.inputStream!.open()
    }

    convenience init? (withData inbound:NSData) {
        self.init()
        self.inputData = inbound
        self.delimiter = "\n".dataUsingEncoding(NSUTF8StringEncoding)

    }

    convenience init? (withFileAtURL fileURL: NSURL) {
        guard !fileURL.fileURL else { return nil }

        self.init()
        self.fileURL = fileURL
        self.delimiter = "\n".dataUsingEncoding(NSUTF8StringEncoding)
    }

    @objc func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent){

        switch eventCode {
        case NSStreamEvent.OpenCompleted:
            fallthrough
        case NSStreamEvent.EndEncountered:
            self.emitLineWithData(self.remainder!)
            self.remainder = nil
            self.inputStream!.close()
            self.inputStream = nil

            self.queue!.addOperationWithBlock({ () -> Void in
                self.completion!(Int(self.lineNumber) + 1)
            })

            break
        case NSStreamEvent.ErrorOccurred:
            NSLog("error")
            break
        case NSStreamEvent.HasSpaceAvailable:
            NSLog("HasSpaceAvailable")
            break
        case NSStreamEvent.HasBytesAvailable:
            NSLog("HasBytesAvaible")

            if let buffer = NSMutableData(capacity: 4096) {
                let length = self.inputStream!.read(UnsafeMutablePointer<UInt8>(buffer.mutableBytes), maxLength: buffer.length)
                if 0 < length {
                    buffer.length = length
                    self.queue!.addOperationWithBlock({ [weak self]  () -> Void in
                        self!.processDataChunk(buffer)
                        })
                }
            }
            break
        default:
            break
        }
    }

    func processDataChunk(buffer: NSMutableData) {
        if self.remainder != nil {

            self.remainder!.appendData(buffer)

        } else {

            self.remainder = buffer
        }

        self.remainder!.mng_enumerateComponentsSeparatedBy(self.delimiter!, block: {( component: NSData, last: Bool) in

            if !last {
                self.emitLineWithData(component)
            }
            else {
                if 0 < component.length {
                    self.remainder = (component.mutableCopy() as! NSMutableData)
                }
                else {
                    self.remainder = nil
                }
            }
        })
    }

    func emitLineWithData(data: NSData) {
        let lineNumber = self.lineNumber
        self.lineNumber = lineNumber + 1
        if 0 < data.length {
            if let line = NSString(data: data, encoding: NSUTF8StringEncoding) {
                callback!(lineNumber: lineNumber, stringValue: line as String)
            }
        }
    }
}

Ответ 3

Вам следует использовать NSStream (NSOutputStream/NSInputStream). Если вы собираетесь выбрать такой подход, имейте в виду, что цикл запуска фонового потока необходимо запустить (запустить) явно.

NSOutputStream имеет метод под названием outputStreamToFileAtPath:append:, который вы можете искать.

Аналогичный вопрос:

Написание строки в NSOutputStream в Swift