web-dev-qa-db-ja.com

バックグラウンドスレッドでディスクに大きなファイルを効率的に書き込む方法(Swift)

更新

気を散らすエラーを解決して削除しました。投稿全体を読んで、質問があればコメントを残してください。

背景

Swift 2.0、GCD、および完了ハンドラーを使用して、iOS上のディスクに比較的大きなファイル(ビデオ)を書き込もうとしています。このタスクを実行するより効率的な方法があるかどうかを知りたいです。完了ロジックを使用しながら、メインUIをブロックせずにタスクを実行する必要があります。また、可能な限り迅速に操作が行われるようにします。 NSDataプロパティを持つカスタムオブジェクトがあるため、現在NSDataで拡張機能を使用して実験しています。例として、別のソリューションには、現在のソリューションのベースとなっているNSData writeToURL関数よりもはるかに高速なスループットをもたらす、ある種のスレッドセーフ動作と組み合わせたNSFilehandleまたはNSStreamsの使用が含まれます。

NSDataとにかく何が問題なのですか?

NSDataクラスリファレンス( Saving Data )からの以下の説明に注意してください。私は一時ディレクトリへの書き込みを実行しますが、問題が発生している主な理由は、大きなファイルを処理するときにUIに顕著な遅延が見られることです。 NSDataは非同期ではないため、この遅延は正確です(およびApple文書では、アトミック書き込みが「大」ファイル〜> 1mbでパフォーマンスの問題を引き起こす可能性があることに注意)。そのため、大きなファイルを扱うときは、NSDataメソッド内で動作している内部メカニズムが何であれ、その恩恵を受けます。

私はもう少し掘り下げて、Appleからこの情報を見つけました... "この方法は、data:// URLをNSDataオブジェクトに変換するのに理想的であり、短いファイルを同期的に読み取るためにも使用できます。潜在的に大きなファイルを読み取り、inputStreamWithURL:を使用してストリームを開き、ファイルを一度に1つずつ読み取ります。」 ( NSDataクラスリファレンス、Objective-C、+ dataWithContentsOfURL )。この情報は、writeToURLをバックグラウンドスレッド(@jtbandesが示唆する)に移動するだけでは不十分な場合、ストリームを使用してバックグラウンドスレッドでファイルを書き出すことができることを暗示しているようです。

NSDataクラスとそのサブクラスは、コンテンツをディスクにすばやく簡単に保存するメソッドを提供します。データ損失のリスクを最小限に抑えるために、これらのメソッドはデータをアトミックに保存するオプションを提供します。アトミック書き込みは、データが完全に保存されるか、完全に失敗することを保証します。アトミック書き込みは、データを一時ファイルに書き込むことから始まります。この書き込みが成功すると、メソッドは一時ファイルを最終的な場所に移動します。

アトミックな書き込み操作は、破損したファイルや部分的に書き込まれたファイルによるデータ損失のリスクを最小限に抑えますが、一時ディレクトリ、ユーザーのホームディレクトリ、または他の公的にアクセス可能なディレクトリへの書き込みには適切でない場合があります。一般にアクセス可能なファイルを使用する場合は常に、そのファイルを信頼できない、潜在的に危険なリソースとして扱う必要があります。攻撃者はこれらのファイルを侵害または破損する可能性があります。攻撃者は、ファイルをハードリンクまたはシンボリックリンクに置き換えて、書き込み操作が他のシステムリソースを上書きまたは破損させる可能性もあります。

一般にアクセス可能なディレクトリ内で作業する場合は、writeToURL:atomically:メソッド(および関連するメソッド)を使用しないでください。代わりに、既存のファイル記述子でNSFileHandleオブジェクトを初期化し、NSFileHandleメソッドを使用してファイルを安全に書き込みます。

その他の選択肢

Objc.ioのConcurrent Programmingの1つの article は、「Advanced:File I/O in the Background」に関する興味深いオプションを提供します。一部のオプションには、InputStreamの使用も含まれます。 Appleには、 非同期でファイルを読み書きする古い参照がいくつかあります 。 Swiftの代替案を見越してこの質問を投稿しています。

適切な回答の例

このタイプの質問を満たす適切な回答の例を次に示します。 (ストリームプログラミングガイド用に作成、 出力ストリームへの書き込み

NSOutputStreamインスタンスを使用して出力ストリームに書き込むには、いくつかの手順が必要です。

  1. 書き込まれたデータのリポジトリを使用して、NSOutputStreamのインスタンスを作成および初期化します。また、デリゲートを設定します。
  2. 実行ループでストリームオブジェクトをスケジュールし、ストリームを開きます。
  3. ストリームオブジェクトがデリゲートに報告するイベントを処理します。
  4. ストリームオブジェクトがメモリにデータを書き込んだ場合、NSStreamDataWrittenToMemoryStreamKeyプロパティを要求してデータを取得します。
  5. 書き込むデータがなくなったら、ストリームオブジェクトを破棄します。

Swift、API、またはC/ObjCで十分なiOSに非常に大きなファイルを書き込むのに適用される最も熟練したアルゴリズムを探しています。アルゴリズムを適切な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
    }
}
        }
     })
}

結論

Core Data Performance のApple Docsは、メモリのプレッシャーへの対処とBLOBの管理に関するいくつかの良いアドバイスを提供します。これは、動作に関する多くの手がかりとアプリ内の大きなファイルの問題を管理する方法を含む記事の実際の1つです。これはファイルではなくコアデータに固有ですが、アトミック書き込みの警告は、アトミックに細心の注意を払って書き込むメソッドを実装する必要があることを示しています。

大きなファイルの場合、書き込みを管理する唯一の安全な方法は、完了ハンドラーを(書き込みメソッドに)追加し、メインスレッドにアクティビティビューを表示することです。ストリームでそれを行うか、既存のAPIを変更して完了ロジックを追加するかは、読者次第です。私は過去に両方をやったことがあり、最高のパフォーマンスを得るためのテストの最中です。

それまでは、ソリューションを変更してコアデータからすべてのバイナリデータプロパティを削除し、それらを文字列に置き換えてディスク上のアセットURLを保持します。また、アセットライブラリとPHAssetの組み込み機能を活用して、関連するすべてのアセットURLを取得して保存します。アセットをコピーする必要がある場合、または必要な場合は、メインスレッドの完了状態をユーザーに通知するために、完了APIを使用して標準APIメソッド(PHAsset/Asset Libraryのエクスポートメソッド)を使用します。

(Core Data Performanceの記事の本当に役立つスニペット)

メモリオーバーヘッドの削減

特定の属性の平均値を計算するなど、一時的に管理対象オブジェクトを使用したい場合があります。これにより、オブジェクトグラフとメモリ消費量が増加します。不要になった個々の管理オブジェクトを再度フォールトすることでメモリのオーバーヘッドを削減するか、管理オブジェクトコンテキストをリセットしてオブジェクトグラフ全体をクリアすることができます。また、Cocoaプログラミング全般に適用されるパターンを使用することもできます。

NSManagedObjectContextのrefreshObject:mergeChanges:メソッドを使用して、個々の管理対象オブジェクトを再度フォールトできます。これには、メモリ内のプロパティ値をクリアして、メモリのオーバーヘッドを削減する効果があります。 (これは、プロパティ値をnilに設定することと同じではないことに注意してください。障害が発生した場合、要求に応じて値が取得されます。障害および一意化を参照してください。)

フェッチリクエストを作成する場合、includesPropertyValuesをNO>に設定して、プロパティ値を表すオブジェクトの作成を回避することでメモリオーバーヘッドを削減できます。ただし、通常は、実際のプロパティデータが不要であるか、行キャッシュにすでに情報がある場合にのみ、そうする必要があります。そうでない場合は、永続ストアへの複数のトリップが発生します。

NSManagedObjectContextのresetメソッドを使用して、コンテキストに関連付けられたすべての管理対象オブジェクトを削除し、作成したかのように「最初からやり直す」ことができます。そのコンテキストに関連付けられている管理対象オブジェクトは無効になるため、まだ参照しているそのコンテキストに関連付けられているオブジェクトへの参照をすべて破棄し、再フェッチする必要があります。多数のオブジェクトを反復処理する場合、ローカルの自動解放プールブロックを使用して、一時オブジェクトができるだけ早く割り当て解除されるようにする必要があります。

Core Dataの取り消し機能を使用しない場合は、コンテキストの取り消しマネージャーをnilに設定することで、アプリケーションのリソース要件を減らすことができます。これは、バックグラウンドワーカースレッド、および大規模なインポート操作またはバッチ操作に特に有益です。

最後に、Core Dataはデフォルトでは管理オブジェクトへの強力な参照を保持しません(未保存の変更がない限り)。メモリ内に多くのオブジェクトがある場合、所有する参照を決定する必要があります。管理対象オブジェクトは、関係を介して相互に強い参照を維持します。これにより、強い参照サイクルを簡単に作成できます。オブジェクトを再度フォールトすることで、サイクルを中断できます(NSManagedObjectContextのrefreshObject:mergeChanges:メソッドを使用して再度)。

大きなデータオブジェクト(BLOB)

アプリケーションが大きなBLOB(画像や音声データなどの「バイナリラージオブジェクト」)を使用する場合、オーバーヘッドを最小限に抑えるように注意する必要があります。 「小」、「控えめ」、「大」の正確な定義は流動的であり、アプリケーションの使用法に依存します。大まかな目安として、サイズがキロバイトのオーダーのオブジェクトは「適度な」サイズであり、メガバイトのオーダーのオブジェクトは「大」サイズです。一部の開発者は、データベース内の10MB BLOBで良好なパフォーマンスを達成しています。一方、アプリケーションの表に数百万の行がある場合、128バイトでさえも、「控えめな」サイズのCLOB(Character Large OBject)であり、別の表に正規化する必要があります。

一般に、BLOBを永続ストアに保存する必要がある場合は、SQLiteストアを使用する必要があります。 XMLおよびバイナリストアでは、オブジェクトグラフ全体がメモリに存在する必要があり、ストアへの書き込みはアトミック(永続ストア機能を参照)です。つまり、大きなデータオブジェクトを効率的に処理しません。 SQLiteは、非常に大きなデータベースを処理するように拡張できます。適切に使用すると、SQLiteは最大100GBのデータベースに対して良好なパフォーマンスを提供し、1行で最大1GBを保持できます(もちろん、1GBのデータをメモリに読み込むのは、リポジトリがどれほど効率的であっても高価な操作です)。

BLOBは多くの場合、エンティティの属性を表します。たとえば、写真はEmployeeエンティティの属性である場合があります。小規模から中程度のサイズのBLOB(およびCLOB)の場合、データ用に別のエンティティを作成し、属性の代わりに1対1の関係を作成する必要があります。たとえば、従業員から写真への関係が従業員の写真属性に置き換わる一対一の関係で、従業員および写真エンティティを作成できます。このパターンは、オブジェクトフォールティングの利点を最大化します(フォールティングと一意化を参照)。特定の写真は、実際に必要な場合にのみ取得されます(関係がトラバースされる場合)。

ただし、BLOBをリソースとしてファイルシステムに保存し、それらのリソースへのリンク(URLやパスなど)を維持できる場合は、より良い方法です。その後、必要に応じてBLOBをロードできます。

注:

以下のロジックを完了ハンドラーに移動し(上記のコードを参照)、エラーは表示されなくなりました。前に述べたように、この質問は、Swiftを使用してiOSで大きなファイルを処理するより高性能な方法があるかどうかに関するものです。

結果の項目配列を処理してUIActvityViewControllerに渡すとき、次のロジックを使用します。

if items.count> 0 {
let sharedActivityView = UIActivityViewController(activityItems:items、applicationActivities:nil)self.presentViewController(sharedActivityView、animated:true){()-> Void in // finished}}

次のエラーが表示されます。通信エラー:{count = 1、contents = "XPCErrorDescription" => {length = 22、contents = "Connection interrupted"}}>(注意してください。このエラーメッセージへの回答)

34
Tommie C.

パフォーマンスは、データがRAMに収まるかどうかに依存します。その場合は、atomically機能を有効にして_NSData writeToURL_を使用する必要があります。

「パブリックディレクトリへの書き込み」は、パブリックディレクトリがないため、iOSではまったく関係ありません。そのセクションはOS Xにのみ適用されます。そして、率直に言って、それも実際には重要ではありません。

したがって、記述したコードは、ビデオがRAM(約100MBが安全な制限)に収まる限り、可能な限り効率的です。

RAMに収まらないファイルの場合、ストリームを使用する必要があります。そうしないと、ビデオをメモリに保持しているときにアプリがクラッシュします。サーバーから大きなビデオをダウンロードしてディスクに書き込むには、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) {
_
20
Abhi Beckert

最新のソリューション(2018)

別の有用な可能性としては、データが追加され、データのストリームの終わりをアナウンスするために、バッファーがいっぱいになったときはいつでもクロージャーを使用することがあります。いくつかのPhoto APIと組み合わせることで、良い結果が得られる可能性があります。そのため、処理中に以下のような宣言コードが起動される可能性があります。

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

管理オブジェクトでこれらのクロージャーを処理すると、メモリを制御しながら、任意のサイズのデータ​​を簡単に処理できる場合があります。

このアイデアを、作業を単一のdispatch_groupに集約する再帰的メソッドの使用と組み合わせると、いくつかのエキサイティングな可能性があります。

Apple docs state:

DispatchGroupは、作業の集約同期を可能にします。これらを使用して、複数の異なるワークアイテムを送信し、それらが異なるキューで実行されている場合でも、それらがすべて完了するタイミングを追跡できます。この動作は、指定されたすべてのタスクが完了するまで進行できない場合に役立ちます。

その他の注目すべきソリューション(〜2016)

これをさらに改良することは間違いありませんが、このトピックは複雑であり、個別の自己回答が必要です。私は他の回答からいくつかのアドバイスを取り、NSStreamサブクラスを活用することにしました。このソリューションは、Obj-Cに基づいています sampleNSInputStream inputStreamWithURLの例ios、2013、May 12)over SampleCodeBank ブログ。

Appleのドキュメントでは、NSStreamサブクラスを使用すると、すべてのデータを一度にメモリにロードする必要がありません。これは、あらゆるサイズのマルチメディアファイルを管理できるようにするための鍵です(使用可能なディスクまたはRAMスペース)を超えないようにします。

NSStreamは、ストリームを表すオブジェクトの抽象クラスです。そのインターフェースは、具象サブクラスNSInputStreamおよびNSOutputStreamを含むすべてのCocoaストリームクラスに共通です。

NSStreamオブジェクトは、デバイスに依存しない方法で、さまざまなメディアとの間でデータを読み書きする簡単な方法を提供します。メモリ、ファイル、またはネットワーク(ソケットを使用)にあるデータのストリームオブジェクトを作成できます。また、すべてのデータを一度にメモリに読み込まずにストリームオブジェクトを使用できます。

ファイルシステムプログラミングガイド

Appleの Streamsを使用したファイル全体の線形処理 FSPGの記事は、NSInputStreamNSOutputStreamが本質的にスレッドセーフであるという概念も提供しました。

file-processing-with-streams

さらなる改良

このオブジェクトは、ストリームの委任メソッドを使用しません。他の改良の余地も十分にありますが、これが基本的なアプローチです。 iPhoneの主な焦点は、バッファを介してメモリを制限しながら大規模ファイル管理を有効にすることです(TBD-outputStreamインメモリバッファの活用)。明確にするために、Appleは、writeToURLの便利な関数が小さなファイルサイズ専用であることを言及しています(しかし、なぜ大きなファイルを処理しないのか不思議に思います-これらはEdgeのケースではありません、注意-バグとして質問を提出します)。

結論

NSStream内部キューイングに干渉したくないので、バックグラウンドスレッドでの統合についてさらにテストする必要があります。ネットワーク上で非常に大きなデータファイルを管理するために、同様のアイデアを使用する他のオブジェクトがいくつかあります。最善の方法は、iOSでファイルサイズをできるだけ小さくして、メモリを節約し、アプリのクラッシュを防ぐことです。 APIはこれらの制約を念頭に置いて構築されているため(無制限のビデオを試すのは得策ではありません)、期待を全体的に調整する必要があります。

Gist Source 、最新の変更については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
    }


}

代表団

バックグラウンドでの高度なファイルI/O 、Eidhof、C。、 ObjC.io )の記事から書き直した同様のオブジェクトを次に示します。わずかな調整で、上記の動作をエミュレートすることができます。 NSOutputStreamメソッドのprocessDataChunkにデータをリダイレクトするだけです。

Gist Source -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)
            }
        }
    }
}
6
Tommie C.

NSStream (NSOutputStream/NSInputStream)の使用を検討する必要があります。このアプローチを選択する場合は、バックグラウンドスレッドの実行ループを明示的に開始(実行)する必要があることに注意してください。

NSOutputStreamには、outputStreamToFileAtPath:append:と呼ばれるメソッドがあります。

同様の質問:

SwiftでNSOutputStreamに文字列を書き込む

2
Anand