2013-03-01 73 views
3

首先,這不是一個重複的問題。我已經閱讀了堆棧溢出的許多問題,但他們沒有幫助完全解決我的問題。下載圖片異步到NSData和緩存

我正在從Web服務下載圖像。由於沒有人喜歡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]; 
    }); 
}); 

如果我究竟使用代碼如上所述,用戶界面將不會停止,直到它從Web服務獲取數據,但圖像沒有緩存。

如果我不使用線程,那麼UI將會停頓,但圖像使用NSCoding方法(存檔)進行緩存。

我的問題是:我能做些什麼來同時使用線程和緩存縮略圖?請不要建議任何第三方庫。

UPDATE:通過代碼一次去再之後,有可能是我能想到的兩個問題:

1)看起來像的NSKeyedArchiver和NSKeyedUnarchiver完成線程之前下載的圖像被稱爲但那只是一個猜測。在一個單獨的存儲文件我使用的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它試圖縮略圖緩存實現解碼後不清爽。

回答

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:^(RSSChannel * obj,NSError * err){ if(!err){

    [channelCopy addItemsFromChannel:obj]; 
        DECREMENT_COUNTER; 
    } 
    block(channelCopy, err); 
    }]; 
    
  2. 每次找到要下載的圖像時遞增計數器,然後在圖像完成下載時遞減計數;當該計數器達到零,你知道你可以存檔:

    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 2013-03-07 00:51:05

+0

奇怪,它正在爲我編譯。嘗試:dispatch_group_async(((RSSChannel *)self.parentParserDelegate).imageDownloadGroup,...' - 只是一個鑄造問題... – sergio 2013-03-07 18:31:12

+0

它的工作。非常感謝真棒幫助,因爲只有你能夠理解問題背後的真實問題。我需要問的最後一件事。它的實施方式,你認爲我需要轉移到核心數據?因爲涉及很多圖像。我打算修剪條目,一旦他們達到100. – AJ112 2013-03-08 02:37:29

0

您是否將thumbnailData作爲實例變量保存?在派遣之前,檢查是否設置了實例變量,如果是,則返回該值。如果沒有,那麼運行你的調度塊並保存爲實例變量。

0

使用此: http://www.markj.net/hjcache-iphone-image-cache/

它做你想要的一切已經。

+0

我在q提到問題,我不想使用第三方庫。 – AJ112 2013-03-06 05:16:15

+0

除非你在蘋果公司工作,否則所有基金會圖書館在技術上都是第三方的。 :) – jsd 2013-03-06 17:38:27

0

假設你的目標是iOS 5+,最合適的解決方案是使用NSURLCacheNSURLConnection +sendAsynchronousRequest:queue:completionHandler:。第三方解決方案通常會忽略這些方法,無論是通過無知還是希望支持iOS 4,所以您的選擇要麼有效地將這些東西維護到Apple,要麼信任第三方,要麼花你自己的時間。

E.g.

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 2013-03-06 05:16:42

+0

什麼例外,並在哪裏?它是即時編寫的,所以我可能在某個地方犯了一個愚蠢的錯誤。 – Tommy 2013-03-06 05:51:07

+1

+ sendAsynchronousRequest的問題:queue:completionHandler:如果您在例如tableview中使用它,則無法取消請求。如果用戶快速滾動,則不想浪費時間完成他們可能永遠不會看到的圖像請求。 – jsd 2013-03-06 17:37:17

0

SDWebImage是圖像緩存的最佳庫。

+1

我在問題中提到我不想使用第三方庫。順便說一句我試過使用它,但我無法查詢已保存的數據,所以它不適用於我 – AJ112 2013-03-06 05:14:58

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]]; 
    }); 
}); 
}