??????在平時(shí)的開(kāi)發(fā)中,經(jīng)常遇到會(huì)UITableView
和UICollectionView
用來(lái)展示列表圖片肤舞,為了提升APP
的流暢度紫新,提高用戶(hù)體驗(yàn),需要開(kāi)發(fā)者在不影響圖片質(zhì)量的條件下作出最優(yōu)的優(yōu)化處理李剖。
1芒率、為什么要做優(yōu)化
一般地,如果不用第三方也不做優(yōu)化處理篙顺,加載一張網(wǎng)絡(luò)圖片的代碼如下:
NSURL *imgURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@",Model.imageURL]];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
cell.imageView.image = [UIImage imageWithData:imgData];
運(yùn)行代碼偶芍,上下滾動(dòng)幾下充择,在手機(jī)和模擬器上都運(yùn)行對(duì)比一下,在網(wǎng)絡(luò)良好的情況下匪蟀,發(fā)現(xiàn)不論上拉還是下拉都很卡椎麦,如下圖是我在iPhone 7
上做的測(cè)試;
分析:上下滑動(dòng)時(shí)造成卡頓的原因
??????1材彪、根據(jù)UITableViewCell
和UICollectionViewCell
的復(fù)用機(jī)制观挎,當(dāng)上拉時(shí)應(yīng)該不會(huì)像下拉時(shí)一樣卡頓,這是為什么呢段化?由于沒(méi)有對(duì)已經(jīng)下載的圖片做緩存處理嘁捷,所以在下拉滑動(dòng)時(shí)和上拉滑動(dòng)是一樣的,把圖片又重新下載了一次显熏。也就是說(shuō)我滑了幾次雄嚣,這張圖片就下載了幾次!
??????如果服務(wù)器存儲(chǔ)的圖片非常大喘蟆,一張圖片可能有幾百M(fèi)缓升,那么下載一張圖片就消耗的時(shí)間很長(zhǎng),即使在網(wǎng)絡(luò)很好的情況下履肃。另外由于沒(méi)有做緩存處理仔沿,每次上下滑動(dòng)坐桩,圖片都會(huì)重新下載尺棋,這樣就會(huì)浪費(fèi)很多流量。
??????2绵跷、由于下載圖片是耗時(shí)操作膘螟,此處是直接放到主線(xiàn)程中操作的,所以下拉時(shí)非衬刖郑卡荆残;
??????這樣帶給用戶(hù)最直接的體驗(yàn)就是APP
的流暢度很差、體驗(yàn)很Low净当。解決這些問(wèn)題内斯,優(yōu)化APP
體驗(yàn)是非常必要的,本文的主要目的就是介紹如何從上面這兩個(gè)角度來(lái)大量圖片的列表做優(yōu)化處理。當(dāng)然對(duì)多圖列表的優(yōu)化要從各個(gè)細(xì)節(jié)入手像啼,本文所用到的是能夠帶來(lái)明顯效果的優(yōu)化處理方式俘闯。對(duì)于更多的深層次的優(yōu)化處理也有很多優(yōu)秀的博客和開(kāi)源框架可以參考。
2忽冻、圖片緩存簡(jiǎn)單處理
(1)真朗、使用NSMutableDictionary
緩存
??????針對(duì)圖片重復(fù)下載的問(wèn)題,先從簡(jiǎn)單的開(kāi)始優(yōu)化僧诚,把已經(jīng)下載的圖片做本地緩存處理遮婶。一般情況下蝗碎,可以用數(shù)組或者字典把已經(jīng)下載的圖片存儲(chǔ)到內(nèi)存中,考慮到每張圖片都是不同的旗扑,這里可以選擇使用字典來(lái)存儲(chǔ)已經(jīng)下載的圖片,由于每行Cell
都是唯一的(當(dāng)然使用圖片的URL
來(lái)標(biāo)記也可以)蹦骑,這里可以使用indexPath.row
來(lái)標(biāo)記圖片,修改代碼如下:
@property (strong, nonatomic) NSMutableDictionary *imgCacheHashMap;
- (NSMutableDictionary *)imgCacheHashMap {
if (_imgCacheHashMap == nil) {
_imgCacheHashMap = [NSMutableDictionary dictionary];
}
return _imgCacheHashMap;
}
UIImage *cacheImg = [self. imgCacheHashMap objectForKey:[NSString stringWithFormat:@"%ld",indexPath.row]];
if (cacheImg) {
cell.bookImg.image = cacheImg;
NSLog(@"======================\n");
} else {
NSURL *imgURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@",Model.images]];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
cell.imageView.image = [UIImage imageWithData:imgData];
[self.imgCacheHashMap setObject:[UIImage imageWithData:imgData] forKey:[NSString stringWithFormat:@"%ld",indexPath.row]];
}
運(yùn)行代碼,如下圖所示臀防,這是在iPhone 8P
上運(yùn)行的效果脊串,可以發(fā)現(xiàn)上拉時(shí)還是和之前一樣非常卡,但是在上拉返回的時(shí)候比之前流暢一些來(lái)清钥。
為了驗(yàn)證數(shù)據(jù)是否成功緩存到了內(nèi)存中琼锋,我把網(wǎng)絡(luò)關(guān)了,然后在重復(fù)上下滑動(dòng)操作祟昭,此時(shí)發(fā)現(xiàn)圖片依然可以加載出來(lái),說(shuō)明緩存是成功的缕坎。
(2)、使用NSCache
緩存
??????通過(guò)上面采用字典緩存的方法篡悟,雖然優(yōu)化了上拉的流暢度谜叹,避免了重復(fù)下載的圖片問(wèn)題,但是這只是對(duì)圖片優(yōu)化處理的第一步搬葬,還需要更多的細(xì)節(jié)需要處理荷腊,iOS
系統(tǒng)為開(kāi)發(fā)者提供了一個(gè)專(zhuān)門(mén)用于緩存的類(lèi)NSCache
類(lèi),NSCache
相對(duì)于NSDictionary
具有更多優(yōu)勢(shì)急凰。
??????《Effective Objective-C 2.0》一書(shū)中第50條也推薦在緩存處理時(shí)采用NSCache
女仰。
??????NSCache
比較重要的兩個(gè)屬性countLimit
和totalCostLimit
,分別用來(lái)設(shè)置緩存數(shù)據(jù)數(shù)量和緩存數(shù)據(jù)占據(jù)內(nèi)存大小,修改代碼如下:
///
@property (strong, nonatomic) NSCache *imgCacheData;
- (NSCache *)imgCacheData {
if (_imgCacheData == nil) {
_imgCacheData = [[NSCache alloc]init];
_imgCacheData.delegate = self;
//設(shè)置緩存數(shù)據(jù)數(shù)量
_imgCacheData.countLimit = LINK_MAX;
//設(shè)置緩存數(shù)據(jù)占據(jù)內(nèi)存大小
_imgCacheData.totalCostLimit = 180 * MAX_CANON * MAX_CANON;
}
return _imgCacheData;
}
- (void)cache:(NSCache *)cache willEvictObject:(id)obj {
printf("\n================== remove old data \n");
}
Apple為開(kāi)發(fā)者的提供NSCache
的使用方法和NSDictionary
是一樣的,在Xcode
中摁住command
鍵點(diǎn)擊NSCache
即可看到系統(tǒng)提供的方法抡锈。
那么NSCache
相對(duì)與NSDictionary
有那些優(yōu)勢(shì)呢疾忍?
1、
NSCache
能在內(nèi)存將要被耗盡時(shí)自動(dòng)清理緩存床三,而不需要開(kāi)發(fā)者在收到內(nèi)存警告時(shí)手動(dòng)清理內(nèi)存一罩;2、NSCache是線(xiàn)程安全的撇簿,也就是說(shuō)可以在不同的線(xiàn)程中訪(fǎng)問(wèn)數(shù)據(jù)聂渊;
3、當(dāng)緩存的數(shù)據(jù)超過(guò)設(shè)置的數(shù)量時(shí)四瘫,NSCache默認(rèn)優(yōu)先移除最先添加的數(shù)據(jù)汉嗽,遵循先進(jìn)先出規(guī)則
為了驗(yàn)證當(dāng)緩存數(shù)據(jù)超過(guò)設(shè)置NSCache
的最大緩存數(shù)量時(shí),系統(tǒng)是如何做內(nèi)存處理的莲组,我做了一個(gè)測(cè)試:
NSCache *testCache = [[NSCache alloc]init];
//設(shè)置緩存數(shù)據(jù)數(shù)量
testCache.countLimit = 5;
for (int i = 0;i< 10;i++) {
[testCache setObject:[NSNumber numberWithInt:i] forKey:[NSString stringWithFormat:@"%d",i]];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
for (int x = 0;x<10;x++) {
NSNumber *number = [testCache objectForKey:[NSString stringWithFormat:@"%d",x]];
NSLog(@"cache data ==============%@",number);
}
});
通過(guò)上面的
Log
可以看出诊胞,設(shè)置testCache
最大緩存數(shù)量為5,但是我添加了10條數(shù)據(jù),此時(shí)可以確定testCache
中只存了5條數(shù)據(jù)撵孤。打印testCache
中的數(shù)據(jù)迈着,可以看出之前緩存的5條數(shù)據(jù)已經(jīng)被清理了。
那么問(wèn)題來(lái)了邪码,當(dāng)用戶(hù)干掉進(jìn)程之后再重新打開(kāi)APP
時(shí)裕菠,之前NSCache
緩存的數(shù)據(jù)還在內(nèi)存中嗎,此處來(lái)做個(gè)測(cè)試闭专,用Log
來(lái)展示奴潘,核心代碼如下:
UIImage *cacheImg = [self.imgCacheData objectForKey:[NSString stringWithFormat:@"%ld",(long)indexPath.row]];
//檢查內(nèi)存緩存
if (cacheImg) {
cell.bookImg.image = cacheImg;
NSLog(@"======================cache\n");
} else {
NSLog(@"=====================download\n");
}
cache
表示從緩存中獲取的圖片,download
表示重新下載的圖片影钉,查看打印的日志當(dāng)我殺掉進(jìn)程重新打開(kāi)APP
時(shí)圖片又重新下載來(lái)一次,根據(jù)打印的日志可以得出結(jié)論:
4画髓、NSCache存儲(chǔ)的數(shù)據(jù),在APP被殺掉或者重啟后緩存的數(shù)據(jù)就沒(méi)有了;
那么平委,有沒(méi)有一種方法奈虾,當(dāng)我下載一次圖片之后就緩存起來(lái)以后再也不用浪費(fèi)流量下載了呢?當(dāng)然可以廉赔,作為開(kāi)發(fā)者肉微,當(dāng)下載完成之后可以把圖片存入硬盤(pán),這樣就不用擔(dān)心APP
被進(jìn)程被干掉后蜡塌,下次打開(kāi)還要繼續(xù)下載圖片的問(wèn)題了碉纳。
3、二級(jí)緩存--圖片硬盤(pán)緩存處理
既然使用NSCache
緩存的圖片在APP
被干掉后再重新打開(kāi)數(shù)據(jù)就沒(méi)有了馏艾,可以把圖片存儲(chǔ)到手機(jī)磁盤(pán)中劳曹,這樣就一勞永逸了,只要不清理攒至,以后都不用再次下載了厚者。那么該如何處理呢躁劣,此處結(jié)合前面的內(nèi)存緩存邏輯迫吐,我整理了一下現(xiàn)在的業(yè)務(wù)邏輯:
- 1、先判斷內(nèi)存中是否有圖片緩存账忘,如果有直接使用志膀;
- 2、判斷手機(jī)硬盤(pán)中是否有圖片緩存鳖擒,如果有直接使用溉浙,并加入到磁盤(pán)緩存中,此處是考慮到從內(nèi)存中取圖片會(huì)比硬盤(pán)中取圖片性能高很多蒋荚;
- 3戳稽、如果手機(jī)硬盤(pán)中沒(méi)有圖片緩存,開(kāi)啟線(xiàn)程去下載,下載完成后刷新列表惊奇,然后在分別加入到內(nèi)存和硬盤(pán)中互躬;
// 獲取沙盒路徑
- (NSString *)getComponentFile:(NSString *)fileName {
//獲取沙盒路徑
NSString *ComponentFileName = [fileName lastPathComponent];
//獲取Cache路徑
NSString *cachePahtStr = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//獲取完整路徑
NSString *fullPathStr = [cachePahtStr stringByAppendingPathComponent:ComponentFileName];
return fullPathStr;
}
在cellForItemAtIndexPath
或cellForRowAtIndexPath
代理中實(shí)現(xiàn)上面的邏輯代碼為:
// 先判斷內(nèi)存中是否有緩存數(shù)據(jù)
UIImage *cacheImg = [weakSelf.imgCacheData objectForKey:[NSString stringWithFormat:@"%ld",(long)indexPath.row]];
// 檢查內(nèi)存緩存
if (cacheImg) {
cell.imageView.image = cacheImg;
NSLog(@"======================cache\n");
} else {
NSString *fullPathStr = [weakSelf getComponentFile:[NSString stringWithFormat:@"%d",(int)indexPath.row]];
// 是否有硬盤(pán)緩存
NSData *imgData = [NSData dataWithContentsOfFile:fullPathStr];
if (imgData) {
NSLog(@"=====================hard disk\n");
// 賦值操作
cell.imageView.image = [UIImage imageWithData:imgData];
// 加入到內(nèi)存中
[weakSelf.imgCacheData setObject:[UIImage imageWithData:imgData] forKey:[NSString stringWithFormat:@"%ld",(long)indexPath.row]];
} else {
NSURL *imgURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@", Model.images]];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
NSLog(@"======================download\n");
//加入內(nèi)存緩存中
[weakSelf.imgCacheData setObject:[UIImage imageWithData:imgData] forKey:[NSString stringWithFormat:@"%ld",(long)indexPath.row]];
//同時(shí)緩存到硬盤(pán)中
[imgData writeToFile:fullPathStr atomically:YES];
//返回主線(xiàn)程
dispatch_async(dispatch_get_main_queue(), ^{
//下載完成后 刷新cell
[collectionView reloadItemsAtIndexPaths:@[indexPath]];
});
}
}
4、圖片異步下載
通過(guò)上面的二級(jí)緩存方式颂郎,已經(jīng)解決了圖片重復(fù)下載的問(wèn)題吼渡,后面就主要解決圖片在第一次下載時(shí)在主線(xiàn)程下載造成的卡頓問(wèn)題,由于下載圖片成功的時(shí)間不確定乓序,跟網(wǎng)絡(luò)寺酪、設(shè)備等一些條件有關(guān),可以把圖片下載操作異步執(zhí)行替劈。
主要思路為:下載圖片時(shí)開(kāi)啟一個(gè)隊(duì)列去下載寄雀,下載完成之后再返回主線(xiàn)程刷新UI,使用GCD
或NSOperationQueue
都可以到達(dá)需求:
///
@property (retain, nonatomic) dispatch_queue_t GCDQueue;
- (dispatch_queue_t)GCDQueue {
if (_GCDQueue == nil) {
_GCDQueue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
}
return _GCDQueue;
}
///
@property (strong, nonatomic) NSOperationQueue *queue;
- (NSOperationQueue *)queue {
if (_queue == nil) {
_queue = [[NSOperationQueue alloc]init];
}
return _queue;
}
在cellForItemAtIndexPath
或cellForRowAtIndexPath
代理方法中實(shí)現(xiàn):
// 強(qiáng)引用 ---> 弱引用
__weak typeof (self) weakSelf = self;
//開(kāi)啟下載隊(duì)列
dispatch_async(weakSelf.GCDQueue, ^{
//圖片下載代碼
//下載完成后加入到內(nèi)存緩存和硬盤(pán)緩存中
//返回主線(xiàn)程刷新列表
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView reloadItemsAtIndexPaths:@[indexPath]];
});
});
// 強(qiáng)引用 ---> 弱引用
__weak typeof (self) weakSelf = self;
//圖片下載代碼
//下載完成后加入到內(nèi)存緩存和硬盤(pán)緩存中
NSBlockOperation *downloadBlock = [NSBlockOperation blockOperationWithBlock:^{
// 返回主線(xiàn)程
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//此處 數(shù)據(jù)源已經(jīng)變了 直接刷新Cell會(huì)再次調(diào)用cellForItemAtIndexPath代理,
[collectionView reloadItemsAtIndexPaths:@[indexPath]];
}];
}];
//加入到隊(duì)列中
[self.queue addOperation:downloadBlock];
5陨献、當(dāng)列表滑動(dòng)時(shí)暫停下載圖片 停止后恢復(fù)下載
??????我們知道咙俩,iOS
系統(tǒng)的RunLoop
可以用來(lái)處理APP
中的各種事件,所以可以用RunLoop
來(lái)監(jiān)聽(tīng)列表的滑動(dòng)操作湿故。
這里也可以使用UIScrollViewDelegate
代理來(lái)監(jiān)聽(tīng)列表的滑動(dòng)操作阿趁,Apple
官方提供的LazyTablbeImages Demo就是用的這個(gè)思路。
方案1:監(jiān)聽(tīng)UIScrollView 的 delegate 回調(diào)
那么現(xiàn)在業(yè)務(wù)邏輯是:
- (1)坛猪、先設(shè)置一張默認(rèn)占位圖片脖阵;
- (2)、在開(kāi)始下載圖片之前先判斷列表是否在滑動(dòng)中墅茉,如果列表正在上下滑動(dòng)命黔,先暫停下載;
-
(3)就斤、等到列表停止滑動(dòng)后再開(kāi)始下載悍募。此處結(jié)合前面的處理邏輯,我畫(huà)了一張流程圖:
業(yè)務(wù)流程圖 2
此處主要在scrollViewDidEndDragging
(用戶(hù)停止拖拽時(shí))和scrollViewDidEndDecelerating
(列表完全停止滑動(dòng)時(shí))這兩個(gè)代理中來(lái)處理洋机,核心代碼為:
// MARK: - download Image
- (void)loadImageForOnscreenRows {
if (self.bookList.count > 0) {
NSArray *visiblePaths = [self.listView indexPathsForVisibleItems];
for (NSIndexPath *indexPath in visiblePaths) {
//============================= get Model data
Model *model = self.dataList[indexPath.row];
[self startImageDownload:model forIndexPath:indexPath];
}
}else{
return;
}
}
//download
- (void)startImageDownload:(Model*)imgModel forIndexPath:(NSIndexPath *)indexPath {
// 強(qiáng)引用 ---> 弱引用
__weak typeof (self) weakSelf = self;
//開(kāi)啟下載隊(duì)列
// dispatch_async(weakSelf.GCDQueue, ^{
//
// //返回主線(xiàn)程
// dispatch_async(dispatch_get_main_queue(), ^{
//
// MainCollectionCell *cell = (MainCollectionCell *)[self.listView cellForItemAtIndexPath:indexPath];
// cell.bookImg.image = [UIImage imageWithData:imgData];
// });
// });
NSBlockOperation *downloadBlock = [NSBlockOperation blockOperationWithBlock:^{
//獲取圖片URL
NSURL *imgURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@",Model.imageURL]];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
NSLog(@"======================download\n");
NSData *finallyImgData = UIImageJPEGRepresentation([UIImage imageWithData:imgData], 0.8);
//
//加入內(nèi)存緩存中
[self.imgCacheData setObject:[UIImage imageWithData:finallyImgData] forKey:[NSString stringWithFormat:@"%ld",(long)indexPath.row]];
//獲取沙盒路徑
NSString *fullPathStr = [weakSelf getComponentFile:[NSString stringWithFormat:@"%ld",(long)indexPath.row]];
//同時(shí)緩存到硬盤(pán)中
[finallyImgData writeToFile:fullPathStr atomically:YES];
// 返回主線(xiàn)程
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//此處 數(shù)據(jù)源已經(jīng)變了 直接刷新Cell會(huì)再次調(diào)用cellForItemAtIndexPath代理坠宴,
[weakSelf.listView reloadItemsAtIndexPaths:@[indexPath]];
}];
}];
//加入到隊(duì)列中
[self.queue addOperation:downloadBlock];
}
// MARK: - UIScrollViewDelegate
//用戶(hù)停止拖拽時(shí)
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if(decelerate) {
[self loadImageForOnscreenRows];
}
}
// 完全停止?jié)L動(dòng)時(shí)
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self loadImageForOnscreenRows];
}
方案2:利用RunLoop優(yōu)化處理
系統(tǒng)為開(kāi)發(fā)者提供的RunLoop
有五種運(yùn)行模式:
運(yùn)行模式 | 備注 |
---|---|
kCFRunLoopDefaultMode |
App的默認(rèn) Mode,通常主線(xiàn)程是在這個(gè) Mode 下運(yùn)行的 |
UITrackingRunLoopMode |
界面跟蹤 Mode绷旗,用于 ScrollView 追蹤觸摸滑動(dòng)喜鼓,保證界面滑動(dòng)時(shí)不受其他 Mode 影響 |
kCFRunLoopCommonModes |
占位Mode,沒(méi)有實(shí)際作用 |
UIInitializationRunLoopMode |
APP 啟動(dòng)時(shí)進(jìn)入的第一個(gè)模式衔肢,啟動(dòng)完成后就不再使用 |
GSEventReceiveRunLoopMode |
接受系統(tǒng)內(nèi)部事件庄岖,通常用不到 |
當(dāng)用戶(hù)滑動(dòng)時(shí),切換到UITrackingRunLoopMode
模式下角骤,此時(shí)NSDefaultRunLoopMode
下的任務(wù)就暫停隅忿,直到再次切換到NSDefaultRunLoopMode
下的時(shí)候,再繼續(xù)之前的下載任務(wù)。這和官方提供的思路雖不同但是也可以到達(dá)優(yōu)化的效果背桐,核心代碼為:
//runLoop tasks blocks
typedef void(^runloopBlock)(void);
// tasks Array
@property (nonatomic, strong) NSMutableArray *tasksList;
// Max tasks
@property (nonatomic, assign) NSUInteger maxTaskCount;
- (void)loadView {
[super loadView];
// Set the maximum number of tasks
self.maxTaskCount = 6;
// create Timer
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(runLoopStayActive)];
// join to RunLoop
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// add runloop observer
[self addRunLoopObserver];
}
// MARK: - ================================= About RunLoop
/// RunLoop Stay active
- (void)runLoopStayActive {
//do-nothing
}
/// RunLoop Observer
- (void)addRunLoopObserver {
// get current now RunLoop
CFRunLoopRef nowRunloop = CFRunLoopGetCurrent();
// create tasks
CFRunLoopObserverContext context = {
0,
(__bridge void *)(self),
&CFRetain,
&CFRelease,
NULL
};
//
static CFRunLoopObserverRef runLoopDefaultModeObserver;
runLoopDefaultModeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopBeforeWaiting,
YES,
0,
&callBack,
&context);
// add Observer for RunLoop
CFRunLoopAddObserver(nowRunloop, runLoopDefaultModeObserver, kCFRunLoopCommonModes);
// memory release
CFRelease(runLoopDefaultModeObserver);
}
///
static void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
MainViewController *mainView = (__bridge MainViewController *)info;
//
if (mainView.tasksList.count == 0) return;
// Take the task from the array
runloopBlock block = [mainView.tasksList firstObject];
// Performing tasks
if (block) {
block();
}
// Remove the task after performing the task
[mainView.tasksList removeObjectAtIndex:0];
}
/// add tasks
- (void)addTasks:(runloopBlock)blocks {
// sava new tasks
[self.tasksList addObject:blocks];
// if the maximum number of tasks is exceeded, remove the previous task
if (self.tasksList.count > self.maxTaskCount) {
[self.tasksList removeObjectAtIndex:0];
}
}
在cellForRowAtIndexPath
或cellForItemAtIndexPath
代理中添加代碼:
[self addTasks:^{
// do samething
}
以上的思路逐步完成了對(duì)一個(gè)列表的優(yōu)化處理刘陶,但是可做優(yōu)化的地方還有很多,各位小伙伴可以閱讀這篇博客iOS 保持界面流暢的技巧牢撼,根據(jù)該文中的所說(shuō)的角度來(lái)優(yōu)化自己的APP
.
因?yàn)楹?jiǎn)書(shū)上傳的圖片不能超過(guò)10M匙隔,所以在視頻轉(zhuǎn)gif
的時(shí)候掉幀嚴(yán)重,建議各位小伙伴運(yùn)行一下本文demo,這樣能很明顯看到優(yōu)化前和優(yōu)化后的效果熏版。
本文是我在列表圖片加載上的一些優(yōu)化處理的總結(jié)纷责,如果有不對(duì)或者有歧義的地方歡迎指出交流。
本文參考了SDWebImage的設(shè)計(jì)思路以及下面的分享和demo
。
Apple Developer LazyTablbeImages Demo
《Effective Objective-C 2.0》第50條:構(gòu)建緩存時(shí)選用NSCache而非NSDictionary
iOS 保持界面流暢的技巧
深入理解RunLoop
iOS線(xiàn)下分享《RunLoop》