淺析IOS-TableView的優(yōu)化

最近這兩天基本就是優(yōu)化,今天想起項目中的tableView感覺體驗不是很好渡贾,一直有卡頓的現(xiàn)象逗宜,數(shù)據(jù)也不多,就找了找網(wǎng)上的優(yōu)化方案空骚,看了不少纺讲,感覺真正有用的不多,稍微做一下小結(jié)囤屹。

項目的列表是自定義的Cell熬甚,用的xib.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"cellID";
    IDBActivityCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if (!cell) {
        cell = [[[NSBundle mainBundle] loadNibNamed:@"IDBActivityCell" owner:self options:nil] lastObject];
    }
    if (self.dataList.count>0) {
        cell.activityModel = self.dataList[indexPath.row];
    }
    
    return cell;
}

感覺有沒有問題,但是其實沒有復(fù)用Cell肋坚,每次都是重建乡括,內(nèi)存開銷大,導(dǎo)致卡頓冲簿,后來更改了復(fù)用的方法粟判。

  1. 用UITableView 的 -registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注冊方法。
    2.[tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath].

之后比之前明顯流暢多了峦剔,這只是目前粗淺的處理辦法档礁。

最開始在CocoaChina上看到的這篇文章

Runloop優(yōu)化列表滑動卡頓

http://www.cocoachina.com/ios/20180228/22365.html
使用的Swift,目前項目用的還是OC吝沫。

利用runloop優(yōu)化呻澜,解決卡頓,具體思路是:

1.創(chuàng)建一個任務(wù)數(shù)組惨险。
2.添加Runloop的監(jiān)聽羹幸。

//MARK:處理卡頓
extension XXXFinancialFroductListVC {
    
    ///添加新的任務(wù)的方法!
    func addTask(_ indexP: IndexPath, unit: @escaping RunloopBlock) {
        self.tasksArr.append(unit)
        self.tasksIndexPathArr.append(indexP)
        //判斷一下 保證沒有來得及顯示的cell不會繪制
        if self.tasksArr.count > self.maxQueueLength {
            _ = self.tasksArr.remove(at: 0)
            _ = self.tasksIndexPathArr.remove(at: 0)
        }
    }
    ///添加runloop的監(jiān)聽
    fileprivate func addRunloopObserver() {
        //獲取當(dāng)前RunLoop
        let runLoop: CFRunLoop = CFRunLoopGetCurrent()
        //定義一個上下文
        var context: CFRunLoopObserverContext = CFRunLoopObserverContext(version: 0, info: unsafeBitCast(self, to: UnsafeMutableRawPointer.self), retain: nil, release: nil, copyDescription: nil)
        //定義一個觀察者
        if   let observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.beforeWaiting.rawValue, true, 0, self.observerCallbackFunc(), &context){
            //添加當(dāng)前RunLoop的觀察者
            CFRunLoopAddObserver(runLoop, observer, .commonModes);
        }
    }

3.在繪制cell的方法中,調(diào)用添加新任務(wù)的方法辫愉,刪除就任務(wù)栅受。
4.通過監(jiān)聽到回調(diào).

<注:監(jiān)聽Runloop的commonModes的Mode切換>

空閑RunLoopMode

當(dāng)用戶正在滑動 UIScrollView(UITableView) 時,RunLoop 將切換到 UITrackingRunLoopMode 接受滑動手勢和處理滑動事件(包括減速和彈簧效果)恭朗,此時屏镊,其他 Mode (除 NSRunLoopCommonModes 這個組合 Mode)下的事件將全部暫停執(zhí)行,來保證滑動事件的優(yōu)先處理痰腮,這也是 iOS 滑動順暢的重要原因而芥。
當(dāng) UI 沒在滑動時,默認(rèn)的 Mode 是 NSDefaultRunLoopMode(同 CF 中的 kCFRunLoopDefaultMode)膀值,同時也是 CF 中定義的 “空閑狀態(tài) Mode”棍丐。當(dāng)用戶啥也不點误辑,此時也沒有什么網(wǎng)絡(luò) IO 時,就是在這個 Mode 下歌逢。

用RunLoopObserver找準(zhǔn)時機

注冊 RunLoopObserver 可以觀測當(dāng)前 RunLoop 的運行狀態(tài)巾钉,并在狀態(tài)機切換時收到通知:

  1. RunLoop開始
  2. RunLoop即將處理Timer
  3. RunLoop即將處理Source
  4. RunLoop即將進入休眠狀態(tài)
  5. RunLoop即將從休眠狀態(tài)被事件喚醒
  6. RunLoop退出

因為“預(yù)緩存”的任務(wù)需要在最無感知的時刻進行,所以應(yīng)該同時滿足:

RunLoop 處于“空閑”狀態(tài) Mode
當(dāng)這一次 RunLoop 迭代處理完成了所有事件趋翻,馬上要休眠時
使用 CF 的帶 block 版本的注冊函數(shù)可以讓代碼更簡潔:

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFStringRef runLoopMode = kCFRunLoopDefaultMode;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
    // TODO here
});
CFRunLoopAddObserver(runLoop, observer, runLoopMode);

分解成多個RunLoop Source任務(wù)

假設(shè)列表有 20 個 cell睛琳,加載后展示了前 5 個盒蟆,那么開啟估算后 table view 只計算了這 5 個的高度踏烙,此時剩下 15 個就是“預(yù)緩存”的任務(wù),而我們并不希望這 15 個計算任務(wù)在同一個 RunLoop 迭代中同步執(zhí)行历等,這樣會卡頓 UI讨惩,所以應(yīng)該把它們分別分解到 15 個 RunLoop 迭代中執(zhí)行,這時就需要手動向 RunLoop 中添加 Source 任務(wù)(由應(yīng)用發(fā)起和處理的是 Source 0 任務(wù))
Foundation 層沒對 RunLoopSource 提供直接構(gòu)建的 API寒屯,但是提供了一個間接的荐捻、既熟悉又陌生的 API:

- (void)performSelector:(SEL)aSelector
               onThread:(NSThread *)thr
             withObject:(id)arg
          waitUntilDone:(BOOL)wait
                  modes:(NSArray *)array;

這個方法將創(chuàng)建一個 Source 0 任務(wù),分發(fā)到指定線程的 RunLoop 中寡夹,在給定的 Mode 下執(zhí)行处面,若指定的 RunLoop 處于休眠狀態(tài),則喚醒它處理事件菩掏,簡單來說就是“睡你xx魂角,起來嗨!”
于是智绸,我們用一個可變數(shù)組裝載當(dāng)前所有需要“預(yù)緩存”的 index path野揪,每個 RunLoopObserver 回調(diào)時都把第一個任務(wù)拿出來分發(fā):

NSMutableArray *mutableIndexPathsToBePrecached = self.fd_allIndexPathsToBePrecached.mutableCopy;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
    if (mutableIndexPathsToBePrecached.count == 0) {
        CFRunLoopRemoveObserver(runLoop, observer, runLoopMode);
        CFRelease(observer); // 注意釋放,否則會造成內(nèi)存泄露
        return;
    }
    NSIndexPath *indexPath = mutableIndexPathsToBePrecached.firstObject;
    [mutableIndexPathsToBePrecached removeObject:indexPath];
    [self performSelector:@selector(fd_precacheIndexPathIfNeeded:)
                 onThread:[NSThread mainThread]
               withObject:indexPath
            waitUntilDone:NO
                    modes:@[NSDefaultRunLoopMode]];
});

這樣瞧栗,每個任務(wù)都被分配到下個“空閑” RunLoop 迭代中執(zhí)行斯稳,其間但凡有滑動事件開始,Mode 切換成 UITrackingRunLoopMode迹恐,所有的“預(yù)緩存”任務(wù)的分發(fā)和執(zhí)行都會自動暫定挣惰,最大程度保證滑動流暢。

PS: 預(yù)緩存功能因為下拉刷新的沖突和不明顯的收益已經(jīng)廢棄

二.

UITableView的優(yōu)化主要從三個方面入手:

  • 提前計算并緩存好高度(布局)殴边,因為heightForRowAtIndexPath:- 是調(diào)用最頻繁的方法憎茂;
  • 異步繪制,遇到復(fù)雜界面找都,遇到性能瓶頸時唇辨,可能就是突破口;
  • 滑動時按需加載(UIScrollView方面)能耻,這個在大量圖片展示赏枚,網(wǎng)絡(luò)加載的時候很管用M龀邸(SDWebImage已經(jīng)實現(xiàn)異步加載,配合這條性能杠杠的)饿幅。

除了上面最主要的三個方面外凡辱,還有很多幾乎大伙都很熟知的優(yōu)化點:

  1. 正確使用reuseIdentifier來重用Cells
  2. 盡量使所有的view opaque,包括Cell自身
  3. 盡量少用或不用透明圖層
  4. 如果Cell內(nèi)現(xiàn)實的內(nèi)容來自web栗恩,使用異步加載透乾,緩存請求結(jié)果
  5. 減少subviews的數(shù)量
  6. 在heightForRowAtIndexPath:中盡量不使用cellForRowAtIndexPath:,如果你需要用到它磕秤,只用一次然后緩存結(jié)果
  7. 盡量少用addView給Cell動態(tài)添加View乳乌,可以初始化時就添加,然后通過hide來控制是否顯示

只是感覺現(xiàn)在手動繪制cell市咆,比較少見汉操。

參考了很多大神優(yōu)秀的文章,匯總蒙兰,不好意思哈A琢觥!!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搜变,一起剝皮案震驚了整個濱河市采缚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挠他,老刑警劉巖扳抽,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绩社,居然都是意外死亡摔蓝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門愉耙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贮尉,“玉大人,你說我怎么就攤上這事朴沿〔卵瑁” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵赌渣,是天一觀的道長魏铅。 經(jīng)常有香客問我,道長坚芜,這世上最難降的妖魔是什么览芳? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮鸿竖,結(jié)果婚禮上沧竟,老公的妹妹穿的比我還像新娘铸敏。我一直安慰自己,他們只是感情好悟泵,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布杈笔。 她就那樣靜靜地躺著,像睡著了一般糕非。 火紅的嫁衣襯著肌膚如雪蒙具。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天朽肥,我揣著相機與錄音禁筏,去河邊找鬼。 笑死鞠呈,一個胖子當(dāng)著我的面吹牛融师,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚁吝,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舀射!你這毒婦竟也來了窘茁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤脆烟,失蹤者是張志新(化名)和其女友劉穎山林,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邢羔,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡驼抹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拜鹤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片框冀。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖敏簿,靈堂內(nèi)的尸體忽然破棺而出明也,到底是詐尸還是另有隱情,我是刑警寧澤惯裕,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布温数,位于F島的核電站,受9級特大地震影響蜻势,放射性物質(zhì)發(fā)生泄漏撑刺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一握玛、第九天 我趴在偏房一處隱蔽的房頂上張望够傍。 院中可真熱鬧次员,春花似錦、人聲如沸王带。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愕撰。三九已至刹衫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搞挣,已是汗流浹背带迟。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留囱桨,地道東北人仓犬。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像舍肠,于是被迫代替她去往敵國和親搀继。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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