最近這兩天基本就是優(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ù)用的方法粟判。
- 用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)機切換時收到通知:
- RunLoop開始
- RunLoop即將處理Timer
- RunLoop即將處理Source
- RunLoop即將進入休眠狀態(tài)
- RunLoop即將從休眠狀態(tài)被事件喚醒
- 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)化點:
- 正確使用reuseIdentifier來重用Cells
- 盡量使所有的view opaque,包括Cell自身
- 盡量少用或不用透明圖層
- 如果Cell內(nèi)現(xiàn)實的內(nèi)容來自web栗恩,使用異步加載透乾,緩存請求結(jié)果
- 減少subviews的數(shù)量
- 在heightForRowAtIndexPath:中盡量不使用cellForRowAtIndexPath:,如果你需要用到它磕秤,只用一次然后緩存結(jié)果
- 盡量少用addView給Cell動態(tài)添加View乳乌,可以初始化時就添加,然后通過hide來控制是否顯示
只是感覺現(xiàn)在手動繪制cell市咆,比較少見汉操。
參考了很多大神優(yōu)秀的文章,匯總蒙兰,不好意思哈A琢觥!!