iOS中的UITableView的重用機(jī)制與加載優(yōu)化詳解
UITableView可以說是UIKit中最重要的一個(gè)組件,用來展示數(shù)據(jù)列表,還可以靈活使用進(jìn)行頁(yè)面的布局。UITableView的使用遵循MVC模式,數(shù)據(jù)模型(NSObject)、視圖(UIView)和控制器(UITableViewController)分離。UITableView繼承自UIScrollView,可上下滑動(dòng),可以作為跟視圖也可以作為子視圖組件。
reuseIdentifier顧名思義是一個(gè)復(fù)用標(biāo)識(shí)符,是一個(gè)自定義的獨(dú)一無二的字符串,用來唯一地標(biāo)記某種重復(fù)樣式的可復(fù)用UITableViewCell,系統(tǒng)是通過reuseIdentifier來確定已經(jīng)創(chuàng)建了的指定樣式的cell來進(jìn)行復(fù)用,iOS中表格的cell通過復(fù)用來提高加載效率,因?yàn)槎鄶?shù)情況下表格中的cell樣式都是重復(fù)的,只是數(shù)據(jù)模型不同而已,因此系統(tǒng)可以在保證創(chuàng)建足夠數(shù)量的cell鋪滿屏幕的前提下,通過保存并重復(fù)使用已經(jīng)創(chuàng)建的cell來提高加載效率和優(yōu)化內(nèi)存,避免不停地創(chuàng)建和銷毀cell元素。
UITableViewCell的復(fù)用原理其實(shí)很簡(jiǎn)單,可以通過下面一個(gè)簡(jiǎn)單的例子來理解:
首先在開發(fā)中我們?cè)赨ITableViewController類中寫cell復(fù)用代碼的最基本模板會(huì)像下面這樣:
/** * 可復(fù)用cell制作 */ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 定義cell重用的靜態(tài)標(biāo)志符 static NSString *cell_id = @"cell_id_demo"; // 優(yōu)先使用可復(fù)用的cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cell_id]; // 如果要復(fù)用的cell還沒有創(chuàng)建,則創(chuàng)建一個(gè)供之后復(fù)用 if (cell == nil) { // 新創(chuàng)建cell并使用cell_id復(fù)用符標(biāo)記 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id]; } // 配置cell數(shù)據(jù) cell.textLabel.text = [NSString stringWithFormat:@"Cell%i", countNumber]; // 其他cell設(shè)置... return cell; }
代碼這樣寫的原因是通過調(diào)用當(dāng)前tableView的dequeueReusableCellWithIdentifier方法看指定的reuseIdentifier是否有可以重復(fù)使用的了,如果有則會(huì)返回可復(fù)用的cell,cell就緒之后便可以開始更新cell的數(shù)據(jù);如果還不可復(fù)用,則返回nil,然后會(huì)進(jìn)入后面的if語(yǔ)句,此時(shí)創(chuàng)建新的cell并對(duì)其設(shè)置cell樣式標(biāo)記reuseIdentifier。注意上面的if語(yǔ)句并不是只要執(zhí)行一次創(chuàng)建一次新的cell就完成任務(wù),然后之后全部重復(fù)利用新創(chuàng)建的那一個(gè)cell,這是對(duì)cell復(fù)用機(jī)制的誤解。
事實(shí)是要?jiǎng)?chuàng)建足夠數(shù)量的可覆蓋整個(gè)tableView的可復(fù)用cell之后才會(huì)開始復(fù)用之前的(UITableView中有一個(gè)visiableCells數(shù)組保存當(dāng)前屏幕可見的cell,還有一個(gè)reusableTableCells數(shù)組用來保存那些可復(fù)用的cell),這個(gè)我們用下面的測(cè)試來驗(yàn)證。
如何簡(jiǎn)潔清楚的展示UITableViewCell的復(fù)用機(jī)制呢?這里的方法是創(chuàng)建最基本的文本cell,并創(chuàng)建一個(gè)cell創(chuàng)建計(jì)數(shù)器,每次新創(chuàng)建cell計(jì)數(shù)器加1并顯示在cell上,如果是復(fù)用的cell則會(huì)顯示是復(fù)用的哪一個(gè)cell,測(cè)試代碼如下:
/** * 分區(qū)個(gè)數(shù)設(shè)置為1 */ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } /** * 創(chuàng)建20個(gè)cell,保證覆蓋并超出整個(gè)tableView */ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 20; } /** * cell復(fù)用機(jī)制測(cè)試 */ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 定義cell重用的靜態(tài)標(biāo)志符 static NSString *cell_id = @"cell_id_demo"; // 計(jì)數(shù)用 static int countNumber = 1; // 優(yōu)先使用可復(fù)用的cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cell_id]; // 如果要復(fù)用的cell還沒有創(chuàng)建,則創(chuàng)建一個(gè)供之后復(fù)用 if (cell == nil) { // 新創(chuàng)建cell并使用cell_id復(fù)用符標(biāo)記 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cell_id]; // 計(jì)數(shù)器標(biāo)記新創(chuàng)建的cell cell.textLabel.text = [NSString stringWithFormat:@"Cell%i", countNumber]; // 計(jì)數(shù)器遞增 countNumber++; } return cell; }
運(yùn)行在iPhone5S設(shè)備上(UITableViewController作為跟控制器,tableView覆蓋整個(gè)屏幕),20個(gè)cell顯示結(jié)果依次為:
Cell1、Cell2、Cell3、Cell4、Cell5、Cell6、Cell7、Cell8、Cell9、Cell10、Cell11、Cell12、Cell13、Cell14、Cell1、Cell2、Cell3、Cell4、Cell5、Cell6
可以看出一共創(chuàng)建了14個(gè)cell,其中整個(gè)屏幕可顯示13個(gè)cell,系統(tǒng)多創(chuàng)建一個(gè)的原因是保證在表格滑動(dòng)顯示半個(gè)cell時(shí)仍然能覆蓋整個(gè)tableView。之后的6個(gè)cell就是復(fù)用了開始創(chuàng)建的那6個(gè)cell了。這樣UITableViewCell復(fù)用的基本機(jī)制就很清楚了,另外還會(huì)有reloadData或者reloadRowsAtIndex等刷新表格數(shù)據(jù)的情況,可能會(huì)伴隨新的cell創(chuàng)建和可復(fù)用cell的更新,但也是建立在基本復(fù)用機(jī)制的基礎(chǔ)之上的。
能否在一個(gè)視圖控制器中嵌入兩個(gè)tableview控制器?
可以,相當(dāng)于視圖以及視圖控制器的嵌套,視圖可以添加子視圖,視圖控制器也可以添加子控制器。這么問應(yīng)該是因?yàn)檫@種情況有時(shí)會(huì)用到而且很重要,因?yàn)橛幸稽c(diǎn)容易被忽視,就是將子視圖添加到了父視圖卻忘記將對(duì)應(yīng)的控制器作為子控制器添加到父控制器,導(dǎo)致子視圖能顯示但是不能響應(yīng)(沒有對(duì)接好控制器)。例如在當(dāng)前視圖上放一個(gè)小尺寸的表格組件,也就是在UIViewController上添加一個(gè)UITableViewController子控制器及其子view:
// 假設(shè)有三個(gè)視圖控制器,一個(gè)作為父控制器,兩個(gè)作為子控制器 UIViewController *superVC = [[UIViewController alloc]init]; UITableViewController *subVC1 = [[UITableViewController alloc]init]; UITableViewController *subVC2 = [[UITableViewController alloc]init]; // 將子視圖控制器添加到父視圖控制器(要注意調(diào)整子視圖的尺寸和位置合理顯示,這里忽略) [superVC.view addSubview:subVC1.view]; [superVC addChildViewController:subVC1]; [superVC.view addSubview:subVC2.view]; [superVC addChildViewController:subVC2]; // 子視圖控制器的移除有對(duì)稱的方法,但只能是子視圖控制器主動(dòng)從父視圖控制器中移除 [subVC1.view removeFromSuperview]; [subVC1 removeFromParentViewController]; [subVC2.view removeFromSuperview]; [subVC2 removeFromParentViewController];
此外要注意和presentViewController函數(shù)添加子視圖控制器的區(qū)別,上面手動(dòng)添加子視圖控制器是可以自由調(diào)整子視圖的frame的(包括子視圖位置和尺寸),而presentViewController是用于頁(yè)面切換,切換后的子頁(yè)面會(huì)覆蓋整個(gè)屏幕而不可以自由調(diào)整子頁(yè)面位置和尺寸,對(duì)稱的子視圖控制器移除方法為dismissViewControllerAnimated:
// 顯示子視圖控制器,completion后的代碼塊如果不為空添加結(jié)束后會(huì)觸發(fā) [[parentVC presentViewController:childVC animated:NO completion:nil]; // 移除子視圖控制器,completion后的代碼塊如果不為空添加結(jié)束后會(huì)觸發(fā) [childVC dismissViewControllerAnimated:NO completion:nil];
一個(gè)tableView是否可以關(guān)聯(lián)兩個(gè)不同的datasource數(shù)據(jù)源?如何處理?
多個(gè)數(shù)據(jù)源是完全可以的,關(guān)鍵是如何關(guān)聯(lián),問題的重點(diǎn)是如何處理,因?yàn)閷?shù)據(jù)源(Model)和tableview視圖(View)的對(duì)接工作是程序員完成的,因此數(shù)據(jù)源的多少?zèng)]有根本影響。處理上可以分開依次對(duì)接,也可以通過數(shù)據(jù)的集合操作先將數(shù)據(jù)整理合并成一個(gè)數(shù)據(jù)源然后對(duì)接。
例如:一個(gè)表格中的每個(gè)cell顯示的是一個(gè)人的基本信息,為了簡(jiǎn)單這里假設(shè)只有一個(gè)頭像和一個(gè)姓名。假設(shè)有兩個(gè)數(shù)據(jù)源,一個(gè)數(shù)據(jù)源是頭像的url數(shù)組,一個(gè)是姓名的字符串?dāng)?shù)組,對(duì)接時(shí)完全可以分開在cell數(shù)據(jù)回調(diào)中對(duì)接,也可以將兩個(gè)數(shù)組合并然后對(duì)接。
合并數(shù)據(jù)用到的數(shù)據(jù)模型:
@interface Model : NSObject @property (nonatomic,copy) NSString *name; // 姓名 @property (nonatomic,copy) NSString *url; // 圖片 @end
數(shù)據(jù)源緩沖器:
// 數(shù)據(jù)源 @property (nonatomic, strong)NSArray *name_datasource; @property (nonatomic, strong)NSArray *url_datasource; @property (nonatomic, strong)NSMutableArray *datasource;
處理多數(shù)據(jù)源:
/** * 請(qǐng)求數(shù)據(jù) */ - (void)request { // 姓名數(shù)據(jù)源 _name_datasource = @[@"張三", @"李四", @"小明", @"小李"]; _url_datasource = @[@"male", @"male", @"male", @"male"]; // 合并數(shù)據(jù)源 for (int i; i<_name_datasource.count; i++) { Model *model = [[Model alloc]init]; model.name = _name_datasource[i]; model.url = _url_datasource[i]; [_datasource addObject:model]; } }
數(shù)據(jù)對(duì)接:
/** * cell數(shù)據(jù)回調(diào) */ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *identifier = @"identifier"; // 自制cell組件 AccountCell *cell = [[AccountCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; /** 多數(shù)據(jù)源分開對(duì)接:**/ // 頭像 [cell.avatar setImage:[UIImage imageNamed:_url_datasource[indexPath.row]]]; // 姓名 cell.name.text = _name_datasource[indexPath.row]; // 或者: /** 數(shù)據(jù)源合并后對(duì)接**/ // 取出對(duì)應(yīng)數(shù)據(jù)模型 Model *model = _datasource[indexPath.row]; // 頭像 [cell.avatar setImage:[UIImage imageNamed:model.url]]; // 姓名 cell.name.text = model.name; return cell; }
如何對(duì)UITableView的滾動(dòng)加載進(jìn)行優(yōu)化,防止卡頓?
UITableView的滾動(dòng)優(yōu)化主要在于以下兩個(gè)方面:
- 減少cellForRowAtIndexPath代理中的計(jì)算量(cell的內(nèi)容計(jì)算)
- 減少heightForRowAtIndexPath代理中的計(jì)算量(cell的高度計(jì)算)
減少cellForRowAtIndexPath代理中的計(jì)算量
- 首先要提前計(jì)算每個(gè)cell中需要的一些基本數(shù)據(jù),代理調(diào)用的時(shí)候直接取出;
- 圖片要異步加載,加載完成后再根據(jù)cell內(nèi)部UIImageView的引用設(shè)置圖片;
- 圖片數(shù)量多時(shí),圖片的尺寸要跟據(jù)需要提前經(jīng)過transform矩陣變換壓縮好(直接設(shè)置圖片的contentMode讓其自行壓縮仍然會(huì)影響滾動(dòng)效率),必要的時(shí)候要準(zhǔn)備好預(yù)覽圖和高清圖,需要時(shí)再加載高清圖。
- 圖片的‘懶加載'方法,即延遲加載,當(dāng)滾動(dòng)速度很快時(shí)避免頻繁請(qǐng)求服務(wù)器數(shù)據(jù)。
- 盡量手動(dòng)Drawing視圖提升流暢性,而不是直接子類化UITableViewCell,然后覆蓋drawRect方法,因?yàn)閏ell中不是只有一個(gè)contentview。繪制cell不建議使用UIView,建議使用CALayer。原因要參考UIView和CALayer的區(qū)別和聯(lián)系。
減少heightForRowAtIndexPath代理中的計(jì)算量
- 由于每次TableView進(jìn)行update更新都會(huì)對(duì)每一個(gè)cell調(diào)用heightForRowAtIndexPath代理取得最新的height,會(huì)大大增加計(jì)算時(shí)間。如果表格的所有cell高度都是固定的,那么去掉heightForRowAtIndexPath代理,直接設(shè)置TableView的rowHeight屬性為固定的高度;
- 如果高度不固定,應(yīng)盡量將cell的高度數(shù)據(jù)計(jì)算好并儲(chǔ)存起來,代理調(diào)用的時(shí)候直接取,即將height的計(jì)算時(shí)間復(fù)雜度降到O(1)。例如:在異步請(qǐng)求服務(wù)器數(shù)據(jù)時(shí),提前將cell高度計(jì)算好并作為dataSource的一個(gè)數(shù)據(jù)存到數(shù)據(jù)庫(kù)供隨時(shí)取用。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS開發(fā)中使用UIDynamic來捕捉動(dòng)畫組件的重力行為
這篇文章主要介紹了iOS開發(fā)中使用UIDynamic來捕捉動(dòng)畫組件的重力行為的方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12實(shí)例講解iOS音樂播放器DOUAudioStreamer用法
本篇文章給大家通過實(shí)例講解了iOS音樂播放器DOUAudioStreamer用法以及分享了實(shí)例代碼,一起學(xué)習(xí)參考下吧。2017-12-12iOS中UITextField實(shí)現(xiàn)過濾選中狀態(tài)拼音的代碼
這篇文章主要介紹了iOS中UITextField實(shí)現(xiàn)過濾選中狀態(tài)拼音的代碼,需要的朋友可以參考下2018-01-01iOS使用pageViewController實(shí)現(xiàn)多視圖滑動(dòng)切換
這篇文章主要為大家詳細(xì)介紹了iOS使用pageViewController實(shí)現(xiàn)多視圖滑動(dòng)切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06iOS block循環(huán)引用詳解及常見誤區(qū)
這篇文章主要介紹了iOS block循環(huán)引用詳解和應(yīng)用,常見誤區(qū)詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08IOS 解決UIButton 點(diǎn)擊卡頓/延遲的問題
本文主要介紹 IOS UIButton, 這里給大家提供代碼實(shí)例作為參考,解決 UIButton 點(diǎn)擊卡頓或者延遲問題,在開發(fā) IOS 項(xiàng)目的小伙伴如果遇到這樣的問題可以參考下2016-07-07配置iOS?16?屏幕旋轉(zhuǎn)適配實(shí)例詳解
這篇文章主要為大家介紹了配置iOS?16?屏幕旋轉(zhuǎn)適配實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09