UITableView性能優(yōu)化-中級篇

老實說咱揍,UITableView性能優(yōu)化 這個話題喉刘,最經(jīng)常遇到的還是在面試中,常見的回答例如:

  • Cell復用機制

  • Cell高度預先計算

  • 緩存Cell高度

  • 圓角切割

  • 等等. . .

image

進階篇

最近遇到一個需求,對tableView有中級優(yōu)化需求

  1. 要求 tableView 滾動的時候,滾動到哪行,哪行的圖片才加載并顯示,滾動過程中圖片不加載顯示;

  2. 頁面跳轉(zhuǎn)的時候渣慕,取消當前頁面的圖片加載請求;

以最常見的cell加載webImage為例:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];    if (!cell) {        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];    }    DemoModel *model = self.datas[indexPath.row];    cell.textLabel.text = model.text;    [cell.imageView setYy_imageURL:[NSURL URLWithString:model.user.avatar_large]];    return cell;}
解釋下cell的復用機制:
  • 如果cell沒進入到界面中(還不可見)抱慌,不會調(diào)用- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath去渲染cell,在cell中如果設置loadImage,不會調(diào)用;

  • 而當cell進去界面中的時候眨猎,再進行cell渲染(無論是init還是從復用池中取)

解釋下YYWebImage機制:
  • 內(nèi)部的YYCache會對圖片進行數(shù)據(jù)緩存抑进,以key:value的形式,這里的key = imageUrl睡陪,value = 下載的image圖片

  • 讀取的時候判斷YYCache中是否有該url寺渗,有的話,直接讀取緩存圖片數(shù)據(jù)兰迫,沒有的話信殊,走圖片下載邏輯,并緩存圖片

問題所在:

如上設置汁果,如果我們cell一行有20行涡拘,頁面啟動的時候,直接滑動到最底部据德,20個cell都進入過了界面鳄乏,- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 被調(diào)用了20次跷车,不符合 需求1的要求

解決辦法:

  1. cell每次被渲染時,判斷當前tableView是否處于滾動狀態(tài)橱野,是的話朽缴,不加載圖片;

  2. cell 滾動結束的時候水援,獲取當前界面內(nèi)可見的所有cell

  3. 在2的基礎之上密强,讓所有的cell請求圖片數(shù)據(jù),并顯示出來

  • 步驟1:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];    if (!cell) {        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];    }    DemoModel *model = self.datas[indexPath.row];    cell.textLabel.text = model.text;    //不在直接讓cell.imageView loadYYWebImage    if (model.iconImage) {        cell.imageView.image = model.iconImage;    }else{        cell.imageView.image = [UIImage imageNamed:@"placeholder"];        //核心判斷:tableView非滾動狀態(tài)下蜗元,才進行圖片下載并渲染        if (!tableView.dragging && !tableView.decelerating) {            //下載圖片數(shù)據(jù) - 并緩存            [ImageDownload loadImageWithModel:model success:^{                //主線程刷新UI                dispatch_async(dispatch_get_main_queue(), ^{                    cell.imageView.image = model.iconImage;                });            }];        }}
  • 步驟2:
- (void)p_loadImage{    //拿到界面內(nèi)-所有的cell的indexpath    NSArray *visableCellIndexPaths = self.tableView.indexPathsForVisibleRows;    for (NSIndexPath *indexPath in visableCellIndexPaths) {        DemoModel *model = self.datas[indexPath.row];        if (model.iconImage) {            continue;        }        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];        [ImageDownload loadImageWithModel:model success:^{            //主線程刷新UI            dispatch_async(dispatch_get_main_queue(), ^{                cell.imageView.image = model.iconImage;            });        }];    }}
  • 步驟3:
//手一直在拖拽控件- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{    [self p_loadImage];}//手放開了-使用慣性-產(chǎn)生的動畫效果- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{    if(!decelerate){        //直接停止-無動畫        [self p_loadImage];    }else{        //有慣性的-會走`scrollViewDidEndDecelerating`方法或渤,這里不用設置    }}

dragging:returns YES if user has started scrolling. this may require some time and or distance to move to initiate dragging

可以理解為,用戶在拖拽當前視圖滾動(手一直拉著)

deceleratingreturns:returns YES if user isn't dragging (touch up) but scroll view is still moving

可以理解為用戶手已放開许帐,試圖是否還在滾動(是否慣性效果)

ScrollView一次拖拽的代理方法執(zhí)行流程:
image

當前代碼生效的效果如下:

點擊查看動圖

RunLoop小操作
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];    if (!cell) {        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];    }    DemoModel *model = self.datas[indexPath.row];    cell.textLabel.text = model.text;    if (model.iconImage) {        cell.imageView.image = model.iconImage;    }else{        cell.imageView.image = [UIImage imageNamed:@"placeholder"];        /**         runloop - 滾動時候 - trackingMode劳坑,         - 默認情況 - defaultRunLoopMode         ==> 滾動的時候,進入`trackingMode`成畦,defaultMode下的任務會暫停         停止?jié)L動的時候 - 進入`defaultMode` - 繼續(xù)執(zhí)行`trackingMode`下的任務 - 例如這里的loadImage         */        [self performSelector:@selector(p_loadImgeWithIndexPath:)                   withObject:indexPath                   afterDelay:0.0                      inModes:@[NSDefaultRunLoopMode]];}//下載圖片距芬,并渲染到cell上顯示- (void)p_loadImgeWithIndexPath:(NSIndexPath *)indexPath{    DemoModel *model = self.datas[indexPath.row];    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];    [ImageDownload loadImageWithModel:model success:^{        //主線程刷新UI        dispatch_async(dispatch_get_main_queue(), ^{            cell.imageView.image = model.iconImage;        });    }];}

效果與demo.gif的效果一致

runloop - 兩種常用模式介紹: trackingMode && defaultRunLoopMode

  • 默認情況 - defaultRunLoopMode
  • 滾動時候 - trackingMode
  • 滾動的時候,進入trackingMode,導致defaultMode下的任務會被暫停,停止?jié)L動的時候 ==> 進入defaultMode - 繼續(xù)執(zhí)行defaultMode下的任務 - 例如這里的defaultMode

大tips:這里循帐,如果使用RunLoop框仔,滾動的時候雖然不執(zhí)行defaultMode,但是滾動一結束拄养,之前cell中的p_loadImgeWithIndexPath就會全部再被調(diào)用离斩,導致類似YYWebImage的效果,其實也是不滿足需求,

  • 提示會被調(diào)用的代碼如下:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{    //p_loadImgeWithIndexPath一進入`NSDefaultRunLoopMode`就會執(zhí)行    [self performSelector:@selector(p_loadImgeWithIndexPath:)               withObject:indexPath               afterDelay:0.0                  inModes:@[NSDefaultRunLoopMode]];}

點擊查看動圖

效果如上

  • 滾動的時候不加載圖片,滾動結束加載圖片-滿足
  • 滾動結束瘪匿,之前滾動過程中的cell會加載圖片 => 不滿足需求

版本回滾到Runloop之前 - git reset --hard runloop之前

解決: 需求2. 頁面跳轉(zhuǎn)的時候跛梗,取消當前頁面的圖片加載請求;

- (void)p_loadImgeWithIndexPath:(NSIndexPath *)indexPath{    DemoModel *model = self.datas[indexPath.row];    //保存當前正在下載的操作    ImageDownload *manager = self.imageLoadDic[indexPath];    if (!manager) {        manager = [ImageDownload new];        //開始加載-保存到當前下載操作字典中        [self.imageLoadDic setObject:manager forKey:indexPath];    }    [manager loadImageWithModel:model success:^{        //主線程刷新UI        dispatch_async(dispatch_get_main_queue(), ^{            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];            cell.imageView.image = model.iconImage;        });        //加載成功-從保存的當前下載操作字典中移除        [self.imageLoadDic removeObjectForKey:indexPath];    }];}- (void)viewWillDisappear:(BOOL)animated{    [super viewWillDisappear:animated];    NSArray *loadImageManagers = [self.imageLoadDic allValues];    //當前圖片下載操作全部取消    [loadImageManagers makeObjectsPerformSelector:@selector(cancelLoadImage)];}@implementation ImageDownload- (void)cancelLoadImage{    [_task cancel];}@end

思路:

  1. 創(chuàng)建一個可變字典棋弥,以indexPath:manager的格式核偿,將當前的圖片下載操作存起來

  2. 每次下載之前,將當前下載線程存入顽染,下載成功后漾岳,將該線程移除

  3. 在viewWillDisappear的時候,取出當前線程字典中的所有線程對象,遍歷進行cancel操作粉寞,完成需求

話外篇:面試題贈送

最近網(wǎng)上各種互聯(lián)網(wǎng)公司裁員信息鋪天蓋地尼荆,甚至包括各種一線公司 ( X東 X乎 都扛不住了嗎-。-)iOS本來就是提前進入寒冬唧垦,iOS小白們可以嘗試思考下這個問題

問:UITableView的圓角性能優(yōu)化如何實現(xiàn)

答:

  1. 讓服務器直接傳圓角圖片捅儒;

  2. 貝塞爾切割控件layer;

  3. YYWebImage為例,可以先下載圖片野芒,再對圖片進行圓角處理蓄愁,再設置到cell上顯示

問:YYWebImage 如何設置圓角? 在下載完成的回調(diào)中?如果你在下載完成的時候再切割,此時 YYWebImage 緩存中的圖片是初始圖片狞悲,還是圓角圖片?(終于等到3了4樽ァ!)

答: 如果是下載完摇锋,在回調(diào)中進行切割圓角的處理丹拯,其實緩存的圖片是原圖,等于每次取的時候荸恕,緩存中取出來的都是矩形圖片乖酬,每次set都得做切割操作;

問: 那是否有解決辦法?

答:其實是有的融求,簡單來說YYWebImage 可以拆分成兩部分咬像,默認情況下,我們拿到的回調(diào)生宛,是走了 download && cache的流程了县昂,這里我們多做一步,取出cache中該url路徑對應的圖片陷舅,進行圓角切割倒彰,再存儲到 cacha中,就能保證以后每次拿到的就都是cacha中已經(jīng)裁切好的圓角圖片

詳情可見:

NSString *path = [[UIApplication sharedApplication].cachesPath stringByAppendingPathComponent:@"weibo.avatar"];YYImageCache *cache = [[YYImageCache alloc] initWithPath:path];manager = [[YYWebImageManager alloc] initWithCache:cache queue:[YYWebImageManager sharedManager].queue];manager.sharedTransformBlock = ^(UIImage *image, NSURL *url) {    if (!image) return image;    return [image imageByRoundCornerRadius:100]; // a large value};

SDWebImage同理莱睁,它有暴露了一個方法出來待讳,可以直接設置保存圖片到磁盤中,無需修改源碼

“winner is coming”,如果面試正好遇到以上問題的仰剿,請叫我雷鋒~
衷心希望各位iOS小伙伴門能熬過這個冬天创淡?

Demo源碼

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市南吮,隨后出現(xiàn)的幾起案子琳彩,更是在濱河造成了極大的恐慌,老刑警劉巖旨袒,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異术辐,居然都是意外死亡砚尽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門辉词,熙熙樓的掌柜王于貴愁眉苦臉地迎上來必孤,“玉大人,你說我怎么就攤上這事》筇拢” “怎么了兴想?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赡勘。 經(jīng)常有香客問我嫂便,道長,這世上最難降的妖魔是什么闸与? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任毙替,我火速辦了婚禮,結果婚禮上践樱,老公的妹妹穿的比我還像新娘厂画。我一直安慰自己,他們只是感情好拷邢,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布袱院。 她就那樣靜靜地躺著,像睡著了一般瞭稼。 火紅的嫁衣襯著肌膚如雪忽洛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天弛姜,我揣著相機與錄音脐瑰,去河邊找鬼。 笑死廷臼,一個胖子當著我的面吹牛苍在,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荠商,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼寂恬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了莱没?” 一聲冷哼從身側響起初肉,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饰躲,沒想到半個月后牙咏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嘹裂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年妄壶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寄狼。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡丁寄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伊磺,我是刑警寧澤盛正,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站屑埋,受9級特大地震影響豪筝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雀彼,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一壤蚜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧徊哑,春花似錦袜刷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梢莽,卻和暖如春萧豆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昏名。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工涮雷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人轻局。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓洪鸭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仑扑。 傳聞我的和親對象是個殘疾皇子览爵,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容