2013-03-01 5 views
3

우선이 질문은 중복되지 않습니다. 스택 오버플로에 대한 많은 질문을 읽었지만 문제를 완전히 해결하는 데 도움이되지 않았습니다.NSData 및 캐시에 비동기 적으로 이미지 다운로드

웹 서비스에서 이미지를 다운로드 중입니다. 어느 누구도 UI가 마음에 들지 않으므로 별도로 이미지를 다운로드하기 위해 스레드를 사용하고 있습니다. 나는 위와 같이 정확하게 코드를 사용하는 경우는 웹 서비스에서 데이터를 가져하지만 이미지가 캐시되지 않습니다 때까지

NSURL *imageUrl = [NSURL URLWithString:storyImageURL]; 

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

    thumbnailData = [NSData dataWithContentsOfURL:imageUrl]; 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     thumbnail = [UIImage imageWithData:thumbnailData]; 
    }); 
}); 

의 UI가 중단되지 않습니다.

스레드를 사용하지 않으면 UI가 정지하지만 이미지는 NSCoding 메서드 (보관)를 사용하여 캐시됩니다.

제 질문은 : 스레드를 사용하고 미리보기 이미지를 동시에 캐시하려면 어떻게해야합니까? 제 3 자 라이브러리를 제안하지 마십시오.

UPDATE :

1) 스레드가 이미지를 다운로드가 완료되기 전에 호출되고있는 NSKeyedArchiver와 NSKeyedUnarchiver 것 같은데 : 또 다시 코드를 통과 한 후, 내가 생각할 수있는 두 가지 문제가있을 수 그러나 그것은 단지 추측이다. 내가 생각할 수있는

- (RSSChannel *)fetchRSSFeedWithCompletion:(void (^)(RSSChannel *, NSError *))block 
{ 
    NSURL *url = [NSURL URLWithString:@"http://techcrunch.com/feed"]; 
    NSURLRequest *req = [NSURLRequest requestWithURL:url]; 

    RSSChannel *channel = [[RSSChannel alloc] init]; 
    TheConnection *connection = [[TheConnection alloc] initWithRequest:req]; 

    //[connection setCompletionBlock:block]; 

    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 
    cachePath = [cachePath stringByAppendingPathComponent:@"HAHAHA.archive"]; 

    RSSChannel *cachedChannel = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath]; 

    if (!cachedChannel) 
     cachedChannel = [[RSSChannel alloc] init]; 

    RSSChannel *channelCopy = [cachedChannel copy]; 

    [connection setCompletionBlock:^(RSSChannel *obj, NSError *err) { 
     if (!err) { 

      [channelCopy addItemsFromChannel:obj]; 
      [NSKeyedArchiver archiveRootObject:channelCopy toFile:cachePath]; 
     } 
     block(channelCopy, err); 
    }]; 


    [connection setXmlRootObject:channel]; 
    [connection start]; 

    return cachedChannel; 
} 

2) 두 번째 문제는 그가 그것을 캐시에서 썸네일을 디코딩 시도 후 새로 고침되지 UI이다 : 별도의 저장소 파일에서 내가 NSKeyedArchiver와 NSKeyedUnarchiver을 사용하고 있습니다.

답변

3

1) NSKeyedArchiver와 NSKeyedUnarchiver처럼 보이는이 호출되고보십시오.별도의 저장소 파일에서 NSKeyedArchiver 및 NSKeyedUnarchiver를 사용하고 있습니다.

여기서 올바른 위치에 있습니다.

원격으로 데이터를 가져 오는 백그라운드 작업과 RSSChannel 간의 동기화 메커니즘이 필요하므로 모든 이미지를 다운로드 한 후에 만 ​​archiveRootObject으로 전화하십시오.

이 문제를 처리하는 한 가지 방법은 모든 이미지 다운로드를 처리하기 위해 dispatch_group을 사용하는 것입니다. 그런 다음 archiveRootObject을 실행하기 전에 완료 블록이 해당 디스패치 그룹에서 대기하도록 만들 수 있습니다. 나는 얼마 전에이 일에 대한 요지를 썼다. 나는 너에게 도움이되어야한다고 생각한다 : https://gist.github.com/sdesimone/4579906. 그렇지 않으면 정확히 무엇을보고하십시오. (아마도 컴파일 오류를 수정해야 할 것입니다).

이 공유 카운터를 관리하는 것입니다 다루는 또 다른 방법 : 당신이

  1. 증가 공급 시작 파싱 완료 블록을 감소 카운터 :

    RSSChannel *channelCopy = [cachedChannel copy]; 
    
    INCREMENT_COUNTER 
    

    [연결을 setCompletionBlock^(OBJ RSSChannel *, * NSError ERR) { 경우 (ERR!) {

    [channelCopy addItemsFromChannel:obj]; 
        DECREMENT_COUNTER; 
    } 
    block(channelCopy, err); 
    }]; 
    
  2. 다운로드 할 이미지를 찾을 때마다 카운터를 증가시킨 다음 이미지 다운로드가 끝나면 카운터를 증가시킵니다. 카운터가 0에 도달 할 때, 당신은 당신이 보관할 수 있습니다 알고

    NSURL *imageUrl = [NSURL URLWithString:storyImageURL]; 
    
    INCREMENT_COUNTER; 
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    
    thumbnailData = [NSData dataWithContentsOfURL:imageUrl]; 
    
    dispatch_async(dispatch_get_main_queue(), ^{ 
        thumbnail = [UIImage imageWithData:thumbnailData]; 
        DECREMENT_COUNTER; 
        if (COUNTER_REACHED_ZERO) 
        CALL_ARCHIVE_METHOD_ON_CHANNEL OBJECT 
        }); 
    }); 
    

이 어떤 리팩토링이 필요합니다 : 당신은 속성 (그래서 당신은 원래의 방법을 외부에서 사용할 수 있습니다로 채널을 저장해야합니다 (포인트 1) 내가 공유 카운터를 구현하는 방법으로 당신에게 결정을두고

가;.!. 단지 구현 스레드 안전하고 돌봐

희망이 도움이

+0

나는 요점에 제공 한 코드를 시도했다. 이 코드를 추가 한 RSSItem.m 아래의 CDATA 블록에서 // - ADD dispatch_group_async ([self.parentParserDelegate imageDownloadGroup], dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{....... 실행 시간 오류 "ARC 시맨틱 문제 - 선택자 'ImageDownloadGroup'에 대한 알려진 인스턴스 메소드가 없습니다. – AJ112

+0

이상한데 컴파일 중입니다.'- 캐스팅 문제 만 ... – sergio

+0

. 질문 뒤에 숨겨진 실제 문제를 이해할 수 있었기 때문에 멋진 도움을 주셔서 감사합니다. 내가 물어야 할 마지막 한가지. 구현 방법, 핵심 데이터로 이동해야합니까? 많은 이미지가 관련되어 있기 때문입니다. 나는 그들이 100에 도달하면 항목을 가지 치기 위해 계획하고있다. – AJ112

0

thumbnailData을 인스턴스 변수로 저장 하시겠습니까? 디스패치하기 전에 인스턴스 변수가 설정되었는지 확인하고, 그렇다면 그 값을 반환하십시오. 그렇지 않으면 파견 블록을 실행하고 인스턴스 변수로 저장하십시오.

0

사용이 : http://www.markj.net/hjcache-iphone-image-cache/

그것은 당신이 이미 원하는 모든 않습니다.

+0

나는 질문에서 언급 나는 제 3 자 라이브러리를 사용하고 싶지 않습니다. – AJ112

+0

Apple에서 일하지 않으면 모든 Foundation 라이브러리가 기술적으로 제 3 자입니다. :) – jsd

0

iOS 5 이상을 타겟팅한다고 가정 할 때 가장 자연스러운 해결책은 NSURLCacheNSURLConnection +sendAsynchronousRequest:queue:completionHandler:을 적절하게 사용하는 것입니다. 제 3 자 솔루션은 일반적으로 무시 또는 아이폰 OS 4를 지원하려는 욕구를 통해 해당 메소드를 무시하기 때문에 Apple에 대한 이러한 유지 관리를 효율적으로 수행하거나 제 3자를 신뢰하거나 자신의 시간을 소비 할 수 있습니다.

예.

NSURLRequest *request = 
    [NSMutableURLRequest requestWithURL:imageURL]; 

// just use the shared cache unless you have special requirements 
NSURLCache *cache = [NSURLCache sharedURLCache]; 

NSCachedURLResponse *response = [cache cachedResponseForRequest:request]; 

// we'll just lazily assume that if anything is in the 
// cache then it will do 
if(response) 
{ 
    [self proceedWithData:response.data]; 
} 
else 
{ 
    // fetch the data 
    [NSURLConnection 
     sendAsynchronousRequest:request 
     queue:[NSOperationQueue mainQueue] // this dictates where the completion 
              // handler is called; it doesn't make 
              // the fetch block the main queue 
     completionHandler: 
      ^(NSURLResponse *response, NSData *data, NSError *error) 
      { 
       // TODO: error handling here 

       [cache 
        storeCachedResponse: 
         [[NSCachedURLResponse alloc] 
          initWithResponse:response dat:data] 
        forRequest:request]; 

        [self proceedWithData:data]; 
      }]; 
} 

NSURLCache

이전에서 iOS 5에 존재하지만 메모리 캐시이었다. 5부터는 디스크 캐시이기도합니다.

+0

나는 이것을 시도했지만 작동하지 않았다. 몇 가지 예외가 있습니다. – AJ112

+0

예외는 무엇이며 어디에서? 나는 어딘가에서 벙어리 실수를 저질렀을 수도 있으므로 즉석에서 작성되었습니다. – Tommy

+1

+ sendAsynchronousRequest : queue : completionHandler의 문제 : 예를 들어, tableview에서 사용중인 경우 요청을 취소 할 수 없습니다. 사용자가 빠르게 스크롤하는 경우 이미지가 표시되지 않을 것으로 예상되는 요청을 마무리하는 데 시간을 낭비하고 싶지 않을 것입니다. – jsd

0

SDWebImage은 이미지 캐싱에 가장 적합한 라이브러리입니다.

+1

제 3 자 라이브러리를 사용하고 싶지 않다는 질문에 언급했습니다. Btw 나는 그것을 사용했으나 나를 위해 작동하지 않았다 그래서 이미 저장된 데이터를 쿼리 할 수 ​​오전 오전 – AJ112

0

스레드가 단지 추측 이미지하지만 그게 전부 다운로드가 완료되기 전에

NSURL *imageUrl = [NSURL URLWithString:storyImageURL]; 

    UIButton *btnThumbnail = [[UIButton alloc] initWithFrame:CGRectMake(0, 10, 180, 280)]; 
    [self downloadingServerImageFromUrl:btnThumbnail AndUrl:imageUrl]; 
    [btnThumbnail addTarget:self action:@selector(onSelectEPaper:) forControlEvents:UIControlEventTouchUpInside]; 
    [self.view addSubview:viewPaperBg]; 

- (void)onSelectEPaper:(id)sender 
    { 
    } 


    -(void)downloadingServerImageFromUrl:(UIButton*)imgView AndUrl:(NSString*)strUrl 
{ 
// strUrl = [strUrl encodeUrl]; 
// strUrl = [strUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 

NSString* theFileName = [NSString stringWithFormat:@"%@.jpg",[[strUrl lastPathComponent] stringByDeletingPathExtension]]; 


NSFileManager *fileManager =[NSFileManager defaultManager]; 
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"tmp/%@",theFileName]]; 


imgView.backgroundColor = [UIColor darkGrayColor]; 
UIActivityIndicatorView *actView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 
[imgView addSubview:actView]; 
[actView startAnimating]; 
CGSize boundsSize = imgView.bounds.size; 
CGRect frameToCenter = actView.frame; 
// center horizontally 
if (frameToCenter.size.width < boundsSize.width) 
    frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width)/2; 
else 
    frameToCenter.origin.x = 0; 

// center vertically 
if (frameToCenter.size.height < boundsSize.height) 
    frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height)/2; 
else 
    frameToCenter.origin.y = 0; 

actView.frame = frameToCenter; 


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
dispatch_async(queue, ^{ 

    NSData *dataFromFile = nil; 
    NSData *dataFromUrl = nil; 

    dataFromFile = [fileManager contentsAtPath:fileName]; 
    //  NSLog(@"%@",fileName); 
    if(dataFromFile==nil){ 
     //  NSLog(@"%@",strUrl); 
     NSString *url =[strUrl stringByReplacingOccurrencesOfString:@"\n" withString:@""]; 
     url=[url stringByReplacingOccurrencesOfString:@"\t" withString:@""]; 
     url=[url stringByReplacingOccurrencesOfString:@" " withString:@""]; 

     url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 
     //  dataFromUrl=[[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]] autorelease]; 
     //  dataFromUrl=[[NSData dataWithContentsOfURL:[NSURL URLWithString:url]] autorelease]; 
     NSError* error = nil; 
     //  NSLog(@"%@", [NSURL URLWithString:url]); 
     dataFromUrl = [NSData dataWithContentsOfURL:[NSURL URLWithString:url] options:NSDataReadingUncached error:&error]; 

     if (error) { 
      NSLog(@"%@", [error localizedDescription]); 
     } else { 
      // NSLog(@"Data has loaded successfully."); 
     } 
    } 

    dispatch_sync(dispatch_get_main_queue(), ^{ 

     if(dataFromFile!=nil){ 
      // imgView.image = [UIImage imageWithData:dataFromFile]; 
      [imgView setBackgroundImage:[UIImage imageWithData:dataFromFile] forState:UIControlStateNormal]; 
     }else if(dataFromUrl!=nil){ 
      // imgView.image = [UIImage imageWithData:dataFromUrl]; 
      [imgView setBackgroundImage:[UIImage imageWithData:dataFromUrl] forState:UIControlStateNormal]; 
      NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"tmp/%@",theFileName]]; 

      BOOL filecreationSuccess = [fileManager createFileAtPath:fileName contents:dataFromUrl attributes:nil]; 
      if(filecreationSuccess == NO){ 
       // NSLog(@"Failed to create the html file"); 
      } 

     }else{ 
      //  imgView.image = [UIImage imageNamed:@"no_image.jpg"]; 
      [imgView setBackgroundImage:[UIImage imageNamed:@"no_image.jpg"] forState:UIControlStateNormal]; 
     } 
     [actView removeFromSuperview]; 
     //   [actView release]; 
     // [imgView setBackgroundColor:[UIColor clearColor]]; 
    }); 
}); 
} 
관련 문제