2

참고 : 저는 ARC를 사용하고 있습니다.AFNetworking NSOperations를 사용하여 여러 파일을 연속적으로 다운로드하는 중 ..... 메모리가 부족합니다.

HTTP 서버에서 JSON을 통해 파일 목록을 요청하는 코드가 있습니다. 그런 다음이 목록을 모델 객체로 구문 분석하여 다운로드 작업 (파일 다운로드 용)을 다른 nsoperationue에 추가 한 다음 모든 작업을 추가하면 (대기열이 일시 중단됨) 대기열에서 빠져 나와 대기합니다. 계속하기 전에 모든 작업을 완료해야합니다. (주 : 이것은 메인 스레드를 막지 않도록 백그라운드 스레드에서 모두 수행됩니다). 다음

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를 실행했는데 누출은 없었지만 메모리가 부족해지기 전에 모든 파일이 다운로드 된 지점에 도달하지 못했습니다. "fat not allocate region"오류가 발생했습니다. 할당을 살펴보면 didFinishLoading 및 connection : didReceiveData 메소드가 연결되지 않는 것으로 나타났습니다. 나는 그것보다 더 그것을 디버깅 할 수없는 것.

내 질문 : 왜 메모리가 부족합니까? 할당 해제되지 않는 것은 무엇이며 어떻게 그렇게 할 수 있습니까?

+0

트위터의 누군가가 [self updateProgress]를 사용하고 있기 때문에 자기가 보유하고 있다고 언급 했으므로 컨트롤러가 소유하고있는 작업 큐 소유의 작업이 소유하고있는 완료 블록 내에 유지됩니다. 컨트롤러 ..... 그래서 유지주기가 있습니다. 직접 [self updateProgress] 코드를 블록으로 직접 옮기면 어떻게 될지 알아야합니다. – valheru

+0

[self updateProgress]를 제거해도 문제가 해결되지 않음을 유의하십시오. – valheru

답변

1

친구의 도움으로 문제를 파악할 수있었습니다.

문제는 코드의 첫 번째 블록에서 실제로이었다

[_dlQueue waitUntilAllOperationsAreFinished]; 

분명히, 모든 작업을 기다리는 것은 그 작업의 의미 것도 하나 발표되지 않을 것이다칩니다.

대신 최종 처리 및 콜백을 수행하는 큐에 최종 작업을 추가하고 메모리가 훨씬 안정적으로되었습니다.

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

        dispatch_async(dispatch_get_main_queue(), ^{ 
         callback(nil); 
        }); 
       }]; 
1

여기에 몇 가지가 있습니다. 가장 큰 방법은 전체 파일을 다운로드하고 메모리에 저장 한 다음 다운로드가 완료되면 디스크에 기록하는 것입니다. 500MB의 파일 한 개로도 메모리가 부족합니다.

올바른 방법은 비동기 다운로드와 함께 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은 매우 제한된 상황에서만 유용합니다. 일반적으로 당신이 필요하다고 절대적으로 확신하지 않는다면, 당신은 그렇지 않습니다.

+0

세 가지 모범 사례가 있습니다. –

+0

감사합니다. NSOutputstream 사용에 대해서는 알고 있지만 보안상의 이유로 파일 보호를 사용하여 디스크에 데이터를 작성해야합니다. – valheru

+0

또한 코드의 __weak 및 autoreleasepool 영역은 일부 작업을 약하게 만들거나 autoreleasepool이 보유하고있는 것이 무엇이든지간에 차이가 나는지 확인하려고 시도하는 영역입니다. 그러나 여기에 제안한 방식을 포함하여 어떤 조합도 작동하지 않았습니다. – valheru

0

어떤 파일을 다운로드하고 있습니까? 이미지를 업로드하지 않을 때 URL 캐시를 지우지 않는 이미지 또는 비디오로 작업하는 경우 CFDATA 및 일부 정보가 캐시에 만들어지며 클리어되지 않습니다. 단일 파일 다운로드가 완료되면이를 명시 적으로 지워야합니다. 결코 누수로 잡히지 않을 것입니다.

NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]; 
    [NSURLCache setSharedURLCache:sharedCache]; 
    [sharedCache release]; 

If you are using ARC replace 
    [sharedCache release]; 
with 
    sharedCache = nil; 

희망 당신을 도울 수 있습니다.

+0

감사합니다. 일부는 이미지이고, 일부는 워드 문서이고, 일부는 HTML 파일, 영화 등입니다. – valheru

+0

좋아요. 당신에게 도움이된다면 대답을 부탁해주십시오. –

+0

슬프게도 도움이되지 않았습니다. – valheru