作者:Love@YR
鏈接:http://blog.csdn.net/jingqiu880905/article/details/51910417
請尊重原創,謝謝!
- 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];
}