解析蘋果官方例子-tableview中圖片懶加載

作者:Love@YR
鏈接:http://blog.csdn.net/jingqiu880905/article/details/51910417
請尊重原創,謝謝!

demo地址:
https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009394

  1. app啓動發送網絡請求以拿到數據
 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:TopPaidAppsFeed]];

    // create an session data task to obtain and the XML feed
    NSURLSessionDataTask *sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:request                                                               completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)                                                                 {...                                                                                                                              ...}];
                                                                 [sessionTask resume];

其中completionHandler block裏如果發現網絡請求有誤,就在主操作隊列裏彈出錯誤的彈框。

[[NSOperationQueue mainQueue] addOperationWithBlock:^{....}];

如果請求成功則進行下一步
2. 子線程裏解析數據

self.queue = [[NSOperationQueue alloc] init];//重新生成一個隊列
_parser = [[ParseOperation alloc] initWithData:data];//自定義一個操作,以網絡請求完傳入的nsdata作爲輸入,用NSXMLParser來解析,以appRecordList屬性作爲輸出,重寫其main方法
/*。。。。。設置解析這個操作的errorhandler和completionBlock(completionBlock這個屬性是NSOperation本身就有的)。其中errorhandler和上面一樣在主線程裏彈錯誤提示框,completionBlock則在主線程裏把拿到的數據(appRecordList)賦值給tableviewController,然後tableview reloadData*/

[self.queue addOperation:self.parser];//開始執行操作

接下來就是tableview的繪製工作
3. 關於RootViewController

  • heightForRowAtIndexPath:經實驗證明此方法執行的次數爲所有cell顯示次數之和,重複滑動會執行很多次。所以這個方法裏不要有大量的運算,最好直接緩存cell高度
  • 關於cell重用
     UITableViewCell *  cell=[tableView dequeueReusableCellWithIdentifier: CellIdentifier];
//        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
        if (!cell) {
//            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
            NSLog(@"jean-----執行次數%d-----",m++);
        }
        cell.XXX=XXXXX;

中間那句alloc也可換成這樣:

  cell = [[[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:nil options:nil] objectAtIndex:0];

官方說不帶forIndexPath的 Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.說明只能拿到已經創建過的,所以需要考慮拿到爲空時自己創建的事情。即需要多個判空的處理

而帶forIndexPath的,則可以不要那句判空。
官方解釋爲 newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered,假定其已經註冊過,如果未註冊用此方法會引起崩潰。

實驗證明:
帶forindexpath:
1. 註冊了cell,從來不爲空,正常走
2. 沒註冊cell,走到dequeueReusableCellWithIdentifier這句就崩潰,所以也不用寫if(!cell)了 已經崩了。

不帶forindexpath:
1. 註冊了cell,只創建了一個cell對象,不會走到if(!cell)裏面因爲cell對象存在,正常走
2. 沒註冊cell , 又沒有在cell爲空時alloc一個,return cell時崩潰說failed to obtain a cell from its dataSource
3. 沒註冊cell,在cell爲空時alloc了一個,會alloc一屏的cell對象,比如此官方例子一屏能顯示13行

註冊方法:

//registerNib (比如viewcontroller的loadView,viewDidLoad中)
 [self.tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:nil] forCellReuseIdentifier:@"CustomCellID”];

//代碼註冊,沒有可視化界面,只需保證此處的ReuseIdentifier跟畫cell時一致即可 (比如customTableView的initWithFrame:style:中)
  [self registerClass:[CustomCell class] forCellReuseIdentifier:@"CustomCellID"];

//storyboard註冊,需要在對應的cell那裏設置其ReuseIdentifier

PS:關於initWithStyle:reuseIdentifier方法和awakeFromNib方法
無xib,用registerClass方法註冊,調用dequeueReusableCellWithIdentifier的時候會觸發initWithStyle:reuseIdentifier方法而不會觸發awakeFromNib
有xib,用registerNib方法註冊,調用dequeueReusableCellWithIdentifier的時候會觸發awakeFromNib方法而不會觸發initWithStyle:reuseIdentifier
無xib,在storyboard裏放置cell,所屬爲CustomCell類, 設置其reuseIdentifier,dequeueReusableCellWithIdentifier的時候會觸發awakeFromNib方法而不會觸發initWithStyle:reuseIdentifier

所以如果正在從nib/storyboard加載你的cell,initWithStyle不會被調用

  • 畫cell的時候,發現如果此行的圖片appIcon已經拿到了就直接顯示,否則判斷如果沒有正在drag也沒有正在減速即scroll已經停下來了 (if (self.tableView.dragging == NO && self.tableView.decelerating == NO))纔去下載圖片,否則就顯示缺省圖。
  • startDownload方法裏下載完成在主線程裏先把圖片scale即縮放到要顯示的大小之後再返回圖片,以此來優化性能。然後再執行傳過來的completionHandler()
  • 在tableview dealloc或者收到內存警告的時候去terminateAllDownloads。此方法讓allDownloads數組裏的object去執行方法即[allDownloads makeObjectsPerformSelector:@selector(cancelDownload)]; 再把數組remove掉。
  • scrollViewDidEndDragging:willDecelerate和scrollViewDidEndDecelerating方法裏loadImagesForOnscreenRows,即拿到可視行,這些行的cell如果沒圖片就去下載。
  • entries是個數組,object是appRecord,當滾到的那行appRecord.appIcon圖片爲空就創建一個iconDownloader去下載,這樣避免了下載完的又去下載。
  • imageDownloadsInProgress是個dictionary,key爲indexPath,value爲iconDownloader,iconDownloader持有appRecord。下載完成後對應的iconDownloader被從imageDownloadsInProgress裏remove掉,加上if(iconDownloader==nil)時纔去下載,這樣就避免了正在下載(但未下載完成)的時候又發起一個同一行的下載任務。
  • 下載完iconDownloader.appRecord.appIcon被賦值,self.entries裏相應的appRecord同時變化,所以不用擔心[self.imageDownloadsInProgress removeObjectForKey:indexPath]之後即此iconDownloader被移除後會發生重複下載,因爲判斷是否需要下載用的是self.entries數組裏的appRecord,而它的appIcon已經有了。
  • 由於上面所述,最後terminateAllDownloads 時候cancel的就是那些加入到下載隊列中但沒有下載完或者還沒有下載的iconDownloader。
IconDownloader *iconDownloader = (self.imageDownloadsInProgress)[indexPath];
    if (iconDownloader == nil) //下載過的就爲nil了就不會再下載
    {
        iconDownloader = [[IconDownloader alloc] init];
        iconDownloader.appRecord = appRecord;
        [iconDownloader setCompletionHandler:^{
            。。。。
            [self.imageDownloadsInProgress removeObjectForKey:indexPath];//下載完就把此iconDownloader remove掉

        }];
        (self.imageDownloadsInProgress)[indexPath] = iconDownloader;
        [iconDownloader startDownload];  
    }
發佈了56 篇原創文章 · 獲贊 28 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章