2

AFNetworking 2.0 にアップデートしたばかりで、データをダウンロードして Core Data に挿入するコードを書き直しています。

JSON データ ファイル (10 ~ 200 MB のファイル) をダウンロードしてディスクに書き込み、バックグラウンド スレッドに渡してデータを処理します。以下は、JSON をダウンロードしてディスクに書き込むコードです。これを (データを処理することさえせずに) そのまま実行すると、アプリは強制終了されるまでメモリを使い果たします。

データが入ってくるとメモリに保存されていると思いますが、ディスクに保存すると、なぜメモリに残るのでしょうか? 自動解放プールがこれを処理するべきではありませんか? また、responseData と downloadData を nil に設定しました。ここで私が間違っていることが露骨に明らかなことはありますか?

@autoreleasepool
{
    for(int i = 1; i <= totalPages; i++)
    {
        NSString *path = ....
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
        AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
        op.responseSerializer =[AFJSONResponseSerializer serializer];

        [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) 
        {
            //convert dictionary to data 
            NSData *downloadData = [NSKeyedArchiver archivedDataWithRootObject:responseObject];

            //save to disk
            NSError *saveError = nil;
            if (![fileManager fileExistsAtPath:targetPath isDirectory:false])
            {
                [downloadData writeToFile:targetPath options:NSDataWritingAtomic error:&saveError];
                if (saveError != nil) 
                {
                    NSLog(@"Download save failed! Error: %@", [saveError description]);
                }
            }

            responseObject = nil;
            downloadData = nil;

        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            DLog(@"Error: %@", error);
        }];
    }
    [mutableOperations addObject:op];
}

NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
    DLog(@"%lu of %lu complete", (unsigned long)numberOfFinishedOperations, (unsigned long)totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
    DLog(@"All operations in batch complete");
}];

mutableOperations = nil;
[manager.operationQueue addOperations:operations waitUntilFinished:NO];

ありがとう!

編集#1@autoreleasepool完全なブロック内に を追加すると、メモリ使用量が少し遅くなるように見えましたが、それでも蓄積され、最終的にアプリがクラッシュします。

4

1 に答える 1

3

JSON ファイルが実際にそれぞれ 10 ~ 200 MB である場合、この種の要求は (永続的なストレージにストリーミングするのではなく) 応答をメモリにロードするため、メモリの問題が確実に発生します。さらに悪いことに、JSON を使用しているため、これをディクショナリ/配列にロードすることになり、メモリも消費するため、問題は 2 倍悪いと思います。したがって、4 つの 100 MB のダウンロードが進行中の場合、ピーク時のメモリ使用量は 800 MB 程度になる可能性があります (100 MB にNSData加えて、配列/辞書 (おそらくもっと大きい) に 100 MB、4 つの同時要求の 4 倍)。 . すぐにメモリ不足になる可能性があります。

だから、いくつかの反応:

  1. この量のデータを処理するときは、ストリーミング インターフェイス (データをメモリに保持するのではなく、入ってくるデータを書き込む場所、またはこれを行うものを使用NSURLConnection)を追求する必要があります。データを永続ストレージに直接保存します (ダウンロード中に RAM内に保持しようとするのではなく)。NSURLSessionDataTaskNSURLSessionDownloadTaskNSData

    を使用するNSURLSessionDownloadTaskと、これは非常に簡単です。7.0 より前のバージョンの iOS をサポートする必要がある場合、AFNetworking が応答の永続ストレージへの直接ストリーミングをサポートしているかどうかはわかりません。それを行う独自の応答シリアライザーを作成できると思いますが、試したことはありません。私は常に、NSURLConnectionDataDelegate永続ストレージに直接ダウンロードする独自のメソッドを作成してきました (たとえば、このようなもの)。

  2. これには JSON を使用したくない場合があります (NSJSONSerializationリソース全体をメモリにロードし、メモリ内のNSArray/に解析するためNSDictionary)。代わりに、応答のストリーミング解析に適した形式 (XML など) を使用します。 RAMにすべてをロードしようとするのではなく、解析中にデータをデータストア(Core DataまたはSQLite)に保存するパーサーを作成します。

    NSXMLParser驚くほどメモリ効率が悪いことに注意してください(この質問を参照してください)。XMLPerformanceサンプルでは、​​Apple は、より扱いにくい LibXML2 を使用して、XMLパーサーのメモリ フットプリントを最小限に抑える方法を示しています。

  3. ちなみに、JSON にエンコードされたバイナリ データ (base 64 など) が含まれているかどうかはわかりませんが、含まれている場合は、これを行う必要のないバイナリ転送形式を検討することをお勧めします。変換。base-64 や uuencode などを使用すると、帯域幅とメモリの要件が増加する可能性があります。(エンコードされたバイナリ データを扱っていない場合は、この点を無視してください。)

  4. 余談ですが、Reachability を使用してユーザーの接続タイプ (Wifi とセルラー) を確認することをお勧めします。これは、速度だけでなく、セルラー経由で大量のデータをダウンロードすることは (少なくともユーザーの許可なしではなく) 不適切な形式と見なされるためです。問題だけでなく、キャリアの月間データ プランの過剰な部分を使い果たすリスクもあります。Apple は歴史的に、セルラー経由で大量のデータをダウンロードしようとするアプリを拒否したと聞いたことさえあります。

于 2014-03-06T19:23:49.073 に答える