4

ファイルをダウンロードする同期メソッド (NSData の initWithContentsOfURL および NSURLConnection の sendSynchronousRequest) を正常に使用するアプリがありますが、大きなファイルをサポートする必要があります。これは、ビットごとにディスクにストリーミングする必要があることを意味します。ディスクへのストリーミングと非同期化は完全に正統な概念であるべきですが、Apple の API では、ストリーミングするために非同期化を余儀なくされています。

明確にするために言うと、私はより大きなファイルのダウンロードを可能にすることを任されており、アプリ全体を再設計してより非同期対応にすることを任されていません。リソースがありません。しかし、再設計に依存するアプローチが有効で優れていることは認めます。

だから、私がこれを行うと:

NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];

..私は最終的に自分自身でdidReceiveResponseとdidReceiveDataを呼び出しました。優秀な。しかし、私がこれをやろうとすると:

NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
while( !self.downloadComplete )
    [ NSThread sleepForTimeInterval: .25 ];

... didReceiveResponse と didReceiveData は呼び出されません。そして、私はその理由を理解しました。奇妙なことに、非同期ダウンロードは、私が使用しているのと同じメイン スレッドで行われます。したがって、メイン スレッドをスリープ状態にすると、作業を行っているものもスリープ状態になります。とにかく、ここで必要なことを達成するために、NSURLConnection に別の NSOperationQueue を使用するように指示したり、dispatch_async を実行して接続を作成して手動で開始したりするなど、いくつかの異なる方法を試しました (これがどのように機能しないのかわかりません-私はそれを正しく行っていなかったに違いありません)が、何も機能していないようです。編集:私が正しく行っていなかったのは、実行ループがどのように機能するか、およびそれらをセカンダリ スレッドで手動で実行する必要があることを理解することでした。

ファイルのダウンロードが完了するまで待つ最善の方法は何ですか?

編集 3、作業コード: 次のコードは実際に動作しますが、より良い方法があれば教えてください。

接続を設定し、ダウンロードの完了を待機する元のスレッドで実行されるコード:

dispatch_queue_t downloadQueue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );
dispatch_async(downloadQueue, ^{
    self.connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ];
    [ [ NSRunLoop currentRunLoop ] run ];
});

while( !self.downloadComplete )
    [ NSThread sleepForTimeInterval: .25 ];

接続イベントに応答する新しいスレッドで実行されるコード:

-(void)connection:(NSURLConnection*) connection didReceiveData:(NSData *)data {
    NSUInteger remainingBytes = [ data length ];
    while( remainingBytes > 0 ) {
        NSUInteger bytesWritten = [ self.fileWritingStream write: [ data bytes ] maxLength: remainingBytes ];
        if( bytesWritten == -1 /*error*/ ) {
            self.downloadComplete = YES;
            self.successful = NO;
            NSLog( @"Stream error: %@", self.fileWritingStream.streamError );
            [ connection cancel ];
            return;
        }
        remainingBytes -= bytesWritten;
    }
}

-(void)connection:(NSURLConnection*) connection didFailWithError:(NSError *)error {
    self.downloadComplete = YES;
    [ self.fileWritingStream close ];
    self.successful = NO;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    self.downloadComplete = YES;
    [ self.fileWritingStream close ];
    self.successful = YES;
}
4

3 に答える 3

5

... didReceiveResponse と didReceiveData は呼び出されません。そして、私はその理由を理解しました。奇妙なことに、非同期ダウンロードは、私が使用しているのと同じメイン スレッドで行われます。新しいスレッドを作成しません。したがって、メイン スレッドをスリープ状態にすると、作業を行っているものもスリープ状態になります。

丁度。接続は実行ループによって駆動されます。スレッドをスリープ状態にすると、実行ループが停止し、接続が機能しなくなります。

だから、特別なことをしないでください。実行ループが実行された状態で、アプリをそこに置いておきます。ユーザーを楽しませるために、画面に小さなスピナーを配置するかもしれません。可能であれば、あなたのビジネスについて行ってください。可能であれば、ユーザーが引き続きアプリケーションを使用できるようにします。接続が完了するとデリゲート メソッドが呼び出され、データに対して必要な操作を実行できます。

コードをバックグラウンド スレッドに移動すると、接続を駆動する実行ループが再び必要になります。したがって、実行ループの作成を開始し、接続をスケジュールしてから、戻るだけです。実行ループは実行され続け、接続が完了するとデリゲート メソッドが再び呼び出されます。スレッドが完了したら、実行ループを停止してスレッドを終了させることができます。それだけです。

例:具体的に言ってみましょう。一度に 1 つずつ、多数の接続を作成したいとしましょう。URL を変更可能な配列に貼り付けます。startNextConnection次のことを行う(たとえば) というメソッドを作成します。

  • 配列から URL を取得します (処理中に削除します)

  • URL リクエストを作成します

  • NSURLConnection を開始します

  • 戻る

また、必要な NSURLConnectionDelegate メソッド、特に を実装しconnectionDidFinishLoading:ます。そのメソッドに次のことをさせます。

  • データをどこかに隠します(ファイルに書き込み、解析のために別のスレッドに渡します)

  • 電話startNextConnection

  • 戻る

エラーが発生しなければ、リスト内のすべての URL のデータを取得するのに十分です。(もちろん、startNextConnectionリストが空になったらすぐに戻るように賢くする必要があります。) ただし、エラーは発生するので、エラーの処理方法を考える必要があります。接続が失敗した場合、プロセス全体を停止しますか? もしそうなら、あなたのconnection:didFailWithError:メソッドに何か適切なことをさせてくださいstartNextConnection。エラーが発生した場合、リストの次の URL にスキップしますか? それから...didFailWithError:電話してstartNextRequestください。

代替手段:同期コードの順次構造を維持したい場合は、次のようになります。

[self downloadURLs];
[self waitForDownloadsToFinish];
[self processData];
...

次に、現在のスレッドを自由にブロックできるように、別のスレッドでダウンロードを行う必要があります。それが必要な場合は、実行ループを使用してダウンロード スレッドを設定します。次に、これまでと同じように接続を作成しますが、最後のパラメーター-initWithRequest:delegate:startImmediately:を渡します。NOを使用-scheduleInRunLoop:forMode:してダウンロード スレッドの実行ループに接続を追加し、-startメソッドで接続を開始します。これにより、現在のスレッドを自由にスリープさせることができます。接続デリゲートの完了ルーチンself.downloadCompleteに、例のフラグなどのフラグを設定させます。

于 2012-05-15T21:14:56.250 に答える
1

非同期モデルを中心にアプリを構築する必要があるという他の回答は正しいため、この回答を提供することをためらっています。それにもかかわらず:

NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSString* myPrivateMode = @"com.yourcompany.yourapp.DownloadMode";
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:myPrivateMode];
[connection start];
while (!self.downloadComplete)
    [[NSRunLoop currentRunLoop] runMode:myPrivateMode beforeDate:[NSDate distantFuture]];

これをメイン スレッドで実行しないでください。アプリは、メモリに大きすぎるファイルをダウンロードした場合と同じように、メイン スレッドをブロックしたために終了する可能性があります。

ところで、メモリではなくファイルにダウンロードしていることを考えると、 からNSURLConnectionへの切り替えを検討する必要がありますNSURLDownload

于 2012-05-16T13:28:47.283 に答える
0

sleepForIntervalあなたは NSURLConnection のアクティビティをブロックしていると思います-

スレッドがブロックされている間、実行ループ処理は発生しません。

NSThreadのドキュメントから。


変数の設定方法を再考する必要があるかもしれないと思いますdownloadCompleteconnectionDidFinishLoading:connectionループ + スリープの代わりに、デリゲート メソッドを使用してダウンロードの完了を判断することを検討してください 。

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{        
    self.downloadComplete = YES;

    // release the connection, and the data object
    [connection release];
    [receivedData release];
}

NSURLConnection ガイドから。

デリゲート メソッドを使用するconnection:connection didFailWithError:errorと、ダウンロードが完了しない状況に確実に対処できます。

于 2012-05-15T20:25:52.010 に答える