12

(更新)これは一言で言えば問題です: iOS では、大きなファイルを読み取り、その上で何らかの処理を行いたい (この特定のケースでは、Base64 string() としてエンコードし、デバイス上の一時ファイルに保存します。セットアップしますファイルから読み込む NSInputStream

(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode

私はほとんどの仕事をしています。何らかの理由で、NSInputStream が機能しなくなることがあります。線があるからわかる

NSLog(@"stream %@ got event %x", stream, (unsigned)eventCode);

の初めに(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode、時には出力が表示されるだけです

stream <__NSCFInputStream: 0x1f020b00> got event 2

(これはイベント NSStreamEventHasBytesAvailable に対応します)、その後は何もありません。NSStreamEventEndEncountered に対応するイベント 10 ではなく、エラー イベントでもありません。また、時にはデバッグ方法がわからない EXC_BAD_ACCESS 例外が発生することもあります。どんな助けでも大歓迎です。

これが実装です。「送信」ボタンを押すとすべてが開始され、トリガーされます。

- (IBAction)submit:(id)sender {     
    [p_spinner startAnimating];    
    [self performSelector: @selector(sendData)
           withObject: nil
           afterDelay: 0];   
}

sendData は次のとおりです。

-(void)sendData{
    ...
    _tempFilePath = ... ;
    [[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil];
    [self setUpStreamsForInputFile: [self.p_mediaURL path] outputFile:_tempFilePath];
    [p_spinner stopAnimating];
    //Pop back to previous VC
    [self.navigationController popViewControllerAnimated:NO] ;
}

上記で呼び出された setUpStreamsForInputFile は次のとおりです。

- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath  {
    self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
    [p_iStream setDelegate:self];
    [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSDefaultRunLoopMode];
    [p_iStream open];   
}

最後に、これはほとんどのロジックが発生する場所です。

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {

    NSLog(@"stream %@ got event %x", stream, (unsigned)eventCode);

    switch(eventCode) {
        case NSStreamEventHasBytesAvailable:
        {
            if (stream == self.p_iStream){
                if(!_tempMutableData) {
                    _tempMutableData = [NSMutableData data];
                }
                if ([_streamdata length]==0){ //we want to write to the buffer only when it has been emptied by the output stream
                    unsigned int buffer_len = 24000;//read in chunks of 24000
                    uint8_t buf[buffer_len];
                    unsigned int len = 0;
                    len = [p_iStream read:buf maxLength:buffer_len];
                    if(len) {
                        [_tempMutableData appendBytes:(const void *)buf length:len];
                        NSString* base64encData = [Base64 encodeBase64WithData:_tempMutableData];
                        _streamdata = [base64encData dataUsingEncoding:NSUTF8StringEncoding];  //encode the data as Base64 string
                        [_tempFileHandle writeData:_streamdata];//write the data
                        [_tempFileHandle seekToEndOfFile];// and move to the end
                        _tempMutableData = [NSMutableData data]; //reset mutable data buffer 
                        _streamdata = [[NSData alloc] init]; //release the data buffer
                    } 
                }
            }
            break;
        case NSStreamEventEndEncountered:
        {
            [stream close];
            [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
                              forMode:NSDefaultRunLoopMode];
            stream = nil;
            //do some more stuff here...
            ...
            break;
        }
        case NSStreamEventHasSpaceAvailable:
        case NSStreamEventOpenCompleted:
        case NSStreamEventNone:
        {
           ...
        }
        }
        case NSStreamEventErrorOccurred:{
            ...
        }
    }
}

注: これを最初に投稿したとき、この問題は GCD の使用に関係していると誤解していました。以下のRobの回答に従って、GCDコードを削除しましたが、問題は解決しません。

4

2 に答える 2

19

まず、元のコードでは、バックグラウンド スレッドではなく、メイン スレッド (dispatch_async がメイン キュー上) を使用していました。

NSInputStream をデフォルトの実行ループ (メイン スレッドの実行ループ) で実行するようにスケジュールすると、メイン スレッドがデフォルト モード (NSDefaultRunLoopMode) のときにイベントが受信されます。

ただし、チェックすると、状況によってはデフォルトのランループがモードを変更します (たとえば、UIScrollView のスクロール中やその他の UI の更新中)。メインの実行ループが NSDefaultRunLoopMode とは異なるモードにある場合、イベントは受信されません。

dispatch_async を使用した古いコードはほとんど問題ありませんでした (ただし、UI の更新はメイン スレッドに移動しました)。いくつかの変更を追加するだけです:

  • 次のようなもので、バックグラウンドでディスパッチします。

:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
 dispatch_async(queue, ^{ 
     // your background code

     //end of your code

     [[NSRunLoop currentRunLoop] run]; // start a run loop, look at the next point
});
  • そのスレッドで実行ループを開始します。これは、このコードを使用して、ディスパッチ非同期呼び出しの最後 (最後の行) で行う必要があります。

:

 [[NSRunLoop currentRunLoop] run]; // note: this method never returns, so it must be THE LAST LINE of your dispatch

試してみて、私に知らせてください

編集 - サンプルコードを追加:

より明確にするために、更新された元のコードをコピーして貼り付けます。

- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath  {
    self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
    [p_iStream setDelegate:self];

    // here: change the queue type and use a background queue (you can change priority)
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_async(queue, ^ {
        [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                       forMode:NSDefaultRunLoopMode];
        [p_iStream open];

        // here: start the loop
        [[NSRunLoop currentRunLoop] run];
        // note: all code below this line won't be executed, because the above method NEVER returns.
    });    
}

この変更を行った後、次のことを行います。

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {}

メソッドは、実行ループを開始した同じスレッド (バックグラウンド スレッド) で呼び出されます。UI を更新する必要がある場合は、メイン スレッドに再度ディスパッチすることが重要です。

追加情報:

私のコードでは、ランダムなバックグラウンド キューで dispatch_async を使用します (使用可能なバックグラウンド スレッドのいずれかにコードをディスパッチするか、必要に応じて新しいスレッドをすべて「自動的に」開始します)。必要に応じて、dispatch async を使用する代わりに独自のスレッドを開始できます。

さらに、「run」メッセージを送信する前に、runloop が既に実行されているかどうかを確認しません (ただし、currentMode メソッドを使用して確認できます。詳細については、NSRunLoop リファレンスを参照してください)。各スレッドには関連付けられた NSRunLoop インスタンスが 1 つしかないため、必要ないはずです。そのため、別の実行 (既に実行されている場合) を送信しても問題はありません :-)

runLoops の直接使用を避けて、dispatch_source を使用して完全な GCD アプローチに切り替えることもできますが、直接使用したことがないため、「良いサンプル コード」を提供することはできません。

于 2013-03-11T18:52:36.740 に答える
4

NSStream実行ループが必要です。GCD は提供していません。ただし、ここでは GCD は必要ありません。NSStreamすでに非同期です。メインスレッドで使用するだけです。そのために設計されています。

また、バックグラウンド スレッドでいくつかの UI 操作を行っています。そんなことはできません。すべての UI インタラクションはメイン スレッドで発生する必要があります (GCD コードを削除すれば簡単です)。

GCD が役立つのは、データの読み取りと処理に時間がかかる場合ですNSStreamEventHasBytesAvailable

于 2013-03-11T17:13:26.567 に答える