[性能優(yōu)化]UITableView性能優(yōu)化的一點感悟及計算UILabel高度的新方法

建了一個面試題解答的項目,大家可以看一看,希望大家?guī)兔o一個star,謝謝了!?

項目地址:https://github.com/NotFound9/interviewGuide?


在使用過程中發(fā)現(xiàn),我們App的首頁在快速滑動時會出現(xiàn)掉幀,以及在上拉加載更多時會抖動,因為首頁模塊是以前的同事寫的,很多代碼已不適應(yīng)當(dāng)前的需求,所以產(chǎn)生了優(yōu)化的想法,優(yōu)化主要分為以下幾個方面:

1.緩存cell高度(發(fā)現(xiàn)了一種計算Label高度的新方法)

2.優(yōu)化cellForRow方法

3.圖片加載優(yōu)化

4.禁止tableView預(yù)估高度

5.刪除無用數(shù)據(jù)處理邏輯

緩存cell高度

在Feed流中,UITableViewCell的高度通常是變化的,需要根據(jù)返回的數(shù)據(jù)中的cell類型以及l(fā)abel的文字長度來計算高度,而在UITableView中func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

是一個高頻調(diào)用的方法,為了減少CPU的計算,盡可能減少掉幀,所以需要將高度進(jìn)行緩存,在我們的項目中,首頁的數(shù)據(jù)是這樣一個操作流程后臺返回的JSON->FeedListModel->FeedsModel->各種cell的ViewModel(例如小圖片的cell對應(yīng)的model-SmallImageCellViewModel,大圖片的cell對應(yīng)的-BigImageCellViewModel)FeedListModel主要是包含了一些頁碼信息和FeedsModel數(shù)組FeedsModel儲存著后臺返回的cell所需的信息BigImageCellViewModel是cell對FeedsModel進(jìn)行處理后得到cell所需的信息

優(yōu)化以前,我們的高度是通過BigImageCellViewModel中計算屬性height去獲取的

var height: CGFloat {

?? guard let title = title else {

? ? ?? return ((UIScreen.mainWidth - 30) * 9)/16.0 + 62

?? }

?? let constraintRect = CGSize(width: UIScreen.mainWidth - 30, height: 38.5)

?? let attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 16)]

?? let rect = title.boundingRect(with: constraintRect,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? options: .usesLineFragmentOrigin,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? attributes: attributes,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? context: nil)

?? if let type = itemType, type == ItemType.sohuVideo {

? ? ?? return ((UIScreen.mainWidth - 30) * 9)/16.0 + rect.height + 62

?? }

?? return ((UIScreen.mainWidth - 30) * 9)/16.0 + rect.height + 62

}

這樣的話每次取值時,會需要通過計算然后返回height屬性,所以一開始我也是把計算屬性改成存儲屬性了,但是還是很耗時(后來才發(fā)現(xiàn)是因為高度是存在BigImageCellViewModel中的,而每次數(shù)據(jù)更新后,由于業(yè)務(wù)需要會對當(dāng)前的列表數(shù)據(jù)重新遍歷處理,生成新的BigImageCellViewModel,新的BigImageCellViewModel的高度自然是每次需要計算),在使用instruments分析時發(fā)現(xiàn),在加載數(shù)據(jù)時,1s內(nèi)有20%的時候是用于計算每個cell的高度,因為計算cell高度時需要根據(jù)model.title確定cell中的標(biāo)題Label顯示幾行,從而確定Label的高度,進(jìn)而算出cell的高度,而計算Label高度一般都是使用這個方法,

@available(iOS 7.0, *)

?? open func boundingRect(with size: CGSize, options: NSStringDrawingOptions = [], attributes: [NSAttributedString.Key : Any]? = nil, context: NSStringDrawingContext?) -> CGRect

因為即便是同一個字符串,字體大小一樣,字體不同時,高度會不一定一樣,這個方法會根據(jù)字符串和對應(yīng)字體進(jìn)行繪制計算后得到的高度,而且這個操作是在主線程進(jìn)行的,所以會導(dǎo)致掉幀,然后我就網(wǎng)上查閱資料怎么優(yōu)化這個方法,網(wǎng)上這方面的資料比較少因為這個方法的耗時本身是可接受范圍以內(nèi)的,只是我們的height沒有真正緩存上導(dǎo)致這個方法測試時特別耗時,這種思路是思路一在子線程中調(diào)用這個方法,然后對height進(jìn)行賦值,類似于這樣:

思路一 異步計算Label高度

? ?var height:CGFloat = 70

? ?let queue = DispatchQueue.global()

?? queue.async {

? ? ?? let labelRect = title.boundingRect(with: constraintRect,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? options: .usesLineFragmentOrigin,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? attributes: attributes,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? context: nil)

? ? ? ?height = labelRect.height + 50

?? }

就是通過先給height賦一個概率最大的值,然后通過異步計算后,得到一個準(zhǔn)確值,再給height賦值上,但是在實際測試中發(fā)現(xiàn),大部分cell取的是我們預(yù)設(shè)的高度默認(rèn)值,這樣在下次reloadData時,cell的高度會取算出來的值,然后會導(dǎo)致tableView的contentSize變化,視圖抖動然后我就自己思考,其實我們的標(biāo)題并不復(fù)雜,大部分是中文,其他是數(shù)字,標(biāo)點符號,字母,然后我就測試了一下在UIFont.boldSystemFont(ofSize: 16)下,中文,數(shù)字,標(biāo)點符號,字母的大小,然后測試發(fā)現(xiàn)中文 15pt 數(shù)字是8pt左右,主要的一些標(biāo)點符號16pt 小寫字母大概8pt,大寫自貿(mào)銀11pt,就想能不能通過對標(biāo)題字符串進(jìn)行遍歷,判斷字符的類型來計算標(biāo)題的總寬度,之后再將總寬度除以標(biāo)題的最大寬度得到行數(shù),然后計算得出cell高度,代碼如下:

思路二 計算Label高度的新方法 通過遍歷字符串來計算高度

-(CGFloat)calculateTotalWidthInBold16 {

?? CGFloat totalWidth = 0;

?? for (int i = 0; i < self.length; i++) {

? ? ?? unichar character? = [self characterAtIndex:i];

? ? ?? //中 占15pt 數(shù)字 占7 英文 a 8.2 A 10.1 B 10.6 , ? 16.6pt

? ? ?? if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:character]) {//數(shù)字

? ? ? ? ?? totalWidth += 8;

? ? ?? } else if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:character]) {//小寫字母

? ? ? ? ?? totalWidth += 10;

? ? ?? } else if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:character]) {//大寫字母

? ? ? ? ?? totalWidth += 12;

? ? ?? } else if ([[NSCharacterSet punctuationCharacterSet] characterIsMember:character]) {//標(biāo)點符號

? ? ? ? ?? totalWidth += 17;

? ? ?? } else if (character >= 0x4E00 && character <= 0x9FA5) {

? ? ? ? ?? totalWidth += 15;

? ? ?? } else {

? ? ? ? ?? totalWidth += 15;

? ? ?? }

?? }

?? return totalWidth + 5;

}

在不緩存高度的情況下,這個方法能夠很快得計算出高度,讓tableview達(dá)到平均55幀以上的幀率,但是缺點是需要對使用的字體下進(jìn)行測試,在UIFont.boldSystemFont(ofSize: 16)字體下,中文是固定的15pt,但是數(shù)字,小寫字母,大寫字母的長度不是固定的,所以如果需要做到非常準(zhǔn)確,需要對每個數(shù)字,字母在這個字體下的長度進(jìn)行測試。

在緩存高度的情況下,與boundingRect方法相比,這個方法也能夠提高計算速度,只是收益不那么明顯

優(yōu)化cellForRow方法

因為tableView的cellForRow方法也是一個調(diào)用頻率特別高的方法,所以應(yīng)該避免在cellForRow對cell進(jìn)行約束修改,frame變化等操作,? ??

open func cellForRow(at indexPath: IndexPath) -> UITableViewCell? // returns nil if cell is not visible or index path is out of range? ??

主要是把這部分代碼注釋掉了,這部分操作主要是為了隱藏最后一個cell的分割線,但是我們是預(yù)加載的,其實很少能看到最后一個cell的底部,所以其實沒有必要

default: //feed流

? ? ? ? ? ?let cellViewModel = viewModel.viewModels.value[indexPath.row]

? ? ? ? ?? let cell = configFeedCell(tableView: tableView, cellViewModel: cellViewModel, indexPath: indexPath)

// ? ? ? ? ?? cell.saHorizontalSpace = (15, 15)

// ? ? ? ? ?? if viewModel.isInfrontOfFeedSpacAble(indexPath: indexPath) {

// ? ? ? ? ?? cell.saSeparaptorLineStyle = .bottom

// ? ? ? ? ?? } else if cellViewModel as? FeedSpacAble != nil {

// ? ? ? ? ? ? ?? cell.saSeparaptorLineStyle = .bottom

// ? ? ? ? ?? } else {

// ? ? ? ? ? ? ?? cell.saSeparaptorLineStyle = .none

// ? ? ? ? ?? }

? ? ? ? ?? return cell

圖片加載優(yōu)化

主要使用charles進(jìn)行抓包,看項目有沒有加載比較大的圖片,我們項目首頁的三張圖片的資訊使用的是大圖,一張圖片長達(dá)4M,所以我改成小圖了

禁止tableView預(yù)估高度

因為tableView會根據(jù)estimatedRowHeight*行數(shù)來計算contentSize,并且在滑動時進(jìn)行修正,所以會發(fā)生抖動,所以可以通過以下代碼,禁用預(yù)估高度,因為iOS11以后預(yù)估高度的值不為0,所以需要顯式賦值為0? ?

? ? ? ?tableView.estimatedRowHeight = 0

? ? ?? tableView.estimatedSectionHeaderHeight = 0

? ? ?? tableView.estimatedSectionFooterHeight = 0

刪除無用數(shù)據(jù)處理邏輯

主要注釋了代碼中沒有用的數(shù)據(jù)處理邏輯

總結(jié)

以上其實只是針對我們項目一些比較基本的優(yōu)化的地方,當(dāng)然還有很多地方可以進(jìn)行優(yōu)化,例如將cell中view的布局進(jìn)行緩存,減少不必要的計算,還有將一些Label通過異步渲染的方式繪制在cell中,減少view的層級,將一部分渲染的工作放在子線程中,但是這樣會對我們的項目改動過大,所以暫時沒有采用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,621評論 2 380

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