2

注: 私は ARC を使用しています。

ファイルのリストを (JSON 経由で) http サーバーに 1 回要求するコードがあります。次に、そのリストをモデル オブジェクトに解析し、ダウンロード操作 (そのファイルをダウンロードするため) を別の nsoperationqueue に追加するために使用します。次に、これらの操作のすべての追加が完了すると (キューは中断された状態で開始されます)、キューを開始して待機します。続行する前にすべての操作を終了します。(注: メインスレッドをブロックしないように、これはすべてバックグラウンドスレッドで行われます)。

基本的なコードは次のとおりです。

NSURLRequest* request = [NSURLRequest requestWithURL:parseServiceUrl];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    //NSLog(@"JSON: %@", responseObject);

    // Parse JSON into model objects

    NSNumber* results = [responseObject objectForKey:@"results"];
    if ([results intValue] > 0)
    {
        dispatch_async(_processQueue, ^{

            _totalFiles = [results intValue];
            _timestamp = [responseObject objectForKey:@"timestamp"];
            NSArray* files = [responseObject objectForKey:@"files"];

            for (NSDictionary* fileDict in files)
            {
                DownloadableFile* file = [[DownloadableFile alloc] init];
                file.file_id = [fileDict objectForKey:@"file_id"];
                file.file_location = [fileDict objectForKey:@"file_location"];
                file.timestamp = [fileDict objectForKey:@"timestamp"];
                file.orderInQueue = [files indexOfObject:fileDict];

                NSNumber* action = [fileDict objectForKey:@"action"];
                if ([action intValue] >= 1)
                {
                    if ([file.file_location.lastPathComponent.pathExtension isEqualToString:@""])
                    {
                        continue;
                    }

                    [self downloadSingleFile:file];
                }
                else // action == 0 so DELETE file if it exists
                {
                    if ([[NSFileManager defaultManager] fileExistsAtPath:file.localPath])
                    {
                        NSError* error;
                        [[NSFileManager defaultManager] removeItemAtPath:file.localPath error:&error];
                        if (error)
                        {
                            NSLog(@"Error deleting file after given an Action of 0: %@: %@", file.file_location, error);
                        }
                    }
                }

                [self updateProgress:[files indexOfObject:fileDict] withTotal:[files count]];

            }

            dispatch_sync(dispatch_get_main_queue(), ^{
                [_label setText:@"Syncing Files..."];
            });

            [_dlQueue setSuspended:NO];
            [_dlQueue waitUntilAllOperationsAreFinished];

            [SettingsManager sharedInstance].timestamp = _timestamp;

            dispatch_async(dispatch_get_main_queue(), ^{
                callback(nil);
            });
        });
    }
    else
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            callback(nil);
        });
    }


} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
    callback(error);
}];

[_parseQueue addOperation:op];

そして downloadSingleFile メソッド:

- (void)downloadSingleFile:(DownloadableFile*)dfile
{
NSURLRequest* req = [NSURLRequest requestWithURL:dfile.downloadUrl];

AFHTTPRequestOperation* reqOper = [[AFHTTPRequestOperation alloc] initWithRequest:req];
reqOper.responseSerializer = [AFHTTPResponseSerializer serializer];

[reqOper setCompletionBlockWithSuccess:^(AFHTTPRequestOperation* op, id response)
 {
         __weak NSData* fileData = response;
         NSError* error;

         __weak DownloadableFile* file = dfile;

         NSString* fullPath = [file.localPath substringToIndex:[file.localPath rangeOfString:file.localPath.lastPathComponent options:NSBackwardsSearch].location];
         [[NSFileManager defaultManager] createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:Nil error:&error];
         if (error)
         {
             NSLog(@"Error creating directory path: %@: %@", fullPath, error);
         }
         else
         {
             error = nil;
             [fileData writeToFile:file.localPath options:NSDataWritingFileProtectionComplete error:&error];
             if (error)
             {
                 NSLog(@"Error writing fileData for file: %@: %@", file.file_location, error);
             }
         }

         [self updateProgress:file.orderInQueue withTotal:_totalFiles];
 }
                               failure:^(AFHTTPRequestOperation* op, NSError* error)
 {
     [self updateProgress:dfile.orderInQueue withTotal:_totalFiles];
     NSLog(@"Error downloading %@: %@", dfile.downloadUrl, error.localizedDescription);
 }];

[_dlQueue addOperation:reqOper];
}

私が見ているのは、より多くのファイルがダウンロードされるにつれて、メモリが常に急増していることです。それはresponseObjectのようなものか、あるいはcompletionBlock全体が手放されていないかもしれません。

fileData と同様に responseObject __weak を作成してみました。autoreleasepool を追加しようとしましたが、実際のファイル ドメイン オブジェクトも __weak にしようとしましたが、それでもメモリがどんどん増えていきます。

Instrumentsを実行しましたが、リークは見られませんでしたが、すべてのファイルがダウンロードされてからメモリが不足し、大きな「領域を割り当てられません」というエラーが発生することはありません。割り当てを見ると、connection:didFinishLoading メソッドと connection:didReceiveData メソッドがたくさんあり、それらは決して手放されないように見えます。それ以上デバッグすることはできないようです。

私の質問: なぜメモリ不足になるのですか? 割り当てが解除されていないものと、そのようにするにはどうすればよいですか?

4

3 に答える 3

1

ここでいくつかのことが起こっています。最大の問題は、ファイル全体をダウンロードしてメモリに保存し、ダウンロードが完了したときにディスクに書き出すことです。500 MB のファイルが 1 つでも、メモリが不足します。

これを行う正しい方法は、非同期ダウンロードで NSOutputStream を使用することです。重要なのは、データが到着したらすぐに書き出すことです。次のようになります。

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.outputStream write:[data bytes] maxLength:[data length]];
}

また、外部ではなく、ブロック内に弱参照を作成していることにも注意してください。そのため、保持サイクルが発生し、メモリ リークが発生しています。弱参照を作成すると、次のようになります。

NSOperation *op = [[NSOperation alloc] init];
__weak NSOperation *weakOp = op;
op.completion = ^{
    // Use only weakOp within this block
};

最後に、コードは を使用して@autoreleasepoolいます。NSAutoreleasePool と、同等の ARC@autoreleasepoolは、非常に限られた状況でのみ役立ちます。原則として、本当に必要かどうかわからない場合は必要ありません。

于 2013-10-19T00:42:19.113 に答える
1

友人の助けを借りて、問題を解決することができました。

問題は、実際にはコードの最初のブロックにありました。

[_dlQueue waitUntilAllOperationsAreFinished];

どうやら、すべての操作が完了するのを待つことは、それらの操作のいずれも解放されないことを意味していました。

その代わりに、最終処理とコールバックを実行する最終操作をキューに追加することになり、メモリは今でははるかに安定しています。

[_dlQueue addOperationWithBlock:^{
                    [SettingsManager sharedInstance].timestamp = _timestamp;

                    dispatch_async(dispatch_get_main_queue(), ^{
                        callback(nil);
                    });
                }];
于 2013-10-23T14:50:24.583 に答える