注意點及細節(jié)處理:
1. 耗時操作放在子線程:下載操作如果耗時較長,在主線程執(zhí)行就會卡死主界面
2. 設置占位圖:由于異步執(zhí)行下載圖片,在設置圖片前Cell的數(shù)據(jù)源方法已經(jīng)執(zhí)行完畢,Cell的ImageView的frame為零,就會導致圖片框不顯示,滾動和點擊Cell后顯示圖片
3. 圖片緩存池:避免移動網(wǎng)絡下重復下載圖片,對已經(jīng)下載的圖片進行緩存處理,當刷新Cell時,從內(nèi)存獲取圖片,執(zhí)行效率更高,并且節(jié)省流量; 放在模型中的缺點是當內(nèi)存緊張時,不方便清理緩存圖片
4. 操作緩存池: 防止同一張圖片多次下載
5. Cell圖片混亂: 當異步操作耗時足夠長,快速滾動Cell時,會從緩存池中獲取Cell,這時的Cell中可能有未執(zhí)行完的任務而導致圖片換亂,解決辦法就是在主線以無動畫的方式程刷新TableView,而不是根據(jù)下載好的圖片進行賦值(在刷新UI前,圖片肯定已經(jīng)下載完成并進行了緩存,刷新后會從緩存中提取圖片)
6. 沙盒本地緩存圖片
7. 使用block時注意循環(huán)引用問題
關鍵代碼:
#import "JSAppsTableController.h"
#import "JSAppsModel.h"
#import "JSAppCell.h"
static NSString * const reuseIdentifier = @"reuseIdentifier";
@implementation JSAppsTableController{
NSArray <JSAppsModel *> *_data; // 數(shù)據(jù)容器
NSOperationQueue *_queue; // 全局隊列
NSMutableDictionary *_operationCache; // 操作緩存池
NSMutableDictionary *_imageCache; // 圖片緩存池
NSString *_cachePath; // 沙盒路徑
}
- (instancetype)initWithFileName:(NSString *)fileName{
self = [super init];
if (self) {
// 成員變量初始化
_data = [JSAppsModel loadAppsDataWithFileName:fileName];
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 5;
_operationCache = [NSMutableDictionary dictionaryWithCapacity:5];
_imageCache = [NSMutableDictionary dictionaryWithCapacity:5];
_cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
}
return self;
}
- (void)viewDidLoad{
[super viewDidLoad];
[self.tableView registerClass:[JSAppCell class] forCellReuseIdentifier:reuseIdentifier];
}
#pragma mark -- UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
JSAppCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath];
JSAppsModel *model = _data[indexPath.row];
cell.textLabel.text = model.name;
cell.detailTextLabel.text = model.download;
// 設置占位圖
cell.imageView.image = [UIImage imageNamed:@"user_default"];
// 從緩存中取出圖片
if ([_imageCache objectForKey:model.icon]) {
cell.imageView.image = [_imageCache objectForKey:model.icon];
NSLog(@"從內(nèi)存圖片緩存池中獲取圖片");
return cell;
}
// 從沙盒中獲取圖片
NSData *data = [NSData dataWithContentsOfFile:[_cachePath stringByAppendingPathComponent:model.icon.lastPathComponent]];
if (data) {
cell.imageView.image = [UIImage imageWithData:data];
// 進行內(nèi)存緩存
[_imageCache setObject:[UIImage imageWithData:data] forKey:model.icon];
NSLog(@"從本地沙盒獲取圖片:(%@)",[_cachePath stringByAppendingPathComponent:model.icon.lastPathComponent]);
return cell;
}
// 判斷操作是否存在
if ([_operationCache objectForKey:model.icon]) {
NSLog(@"圖片正在下載中...");
return cell;
}
/* block內(nèi)部使用self,不能夠說一定存在循環(huán)引用
self -> _operationCache和_queue -> downLoadImageOperation -> block -> self
_operationCache -> 手動清除downLoadImageOperation
_queue -> 當隊列中的操作執(zhí)行完畢后downLoadImageOperation會自動銷毀
*/
//__weak typeof(self) weakSelf = self;
// 異步下載圖片
NSBlockOperation *downLoadImageOperation = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:5]; // 模擬延遲
// 下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:model.icon]];
UIImage *image = [UIImage imageWithData:data];
// 將下載好的圖片進行內(nèi)存緩存
// model.downloadImage = image;
[_imageCache setObject:image forKey:model.icon];
// 將下載好的圖片做本地緩存
[data writeToFile:[_cachePath stringByAppendingPathComponent:model.icon.lastPathComponent] atomically:YES];
// 返回主線程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// cell.imageView.image = image; 避免重用Cell中有正在執(zhí)行的下載操作導致圖片混亂,直接刷新TableView從內(nèi)存獲取圖片
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
// 清除操作緩存池中對應的操作
[_operationCache removeObjectForKey:model.icon];
/* [_operationCache removeAllObjects];
假設操作的完成時間足夠長,因為下載操作異步執(zhí)行,CPU會隨機執(zhí)行線程上的操作,如果設置了優(yōu)先級或執(zhí)行某一線程的概率較高,那么可以肯定,完成有先后,只是不夠明顯
一旦某個操作提前完成執(zhí)行了清空操作緩存池,當再次滾動TableView的時候,可能還會出現(xiàn)同一個下載操作重復添加到隊列中的問題
所以不應該使用removeAllObjects方法來移除
*/
}];
}];
// 將操作添加到隊列中: 隊列當中的操作執(zhí)行完畢之后,會自動從隊列中銷毀
[_queue addOperation:downLoadImageOperation];
// 將每一個下載圖片的操作都添加到操作緩存池中(如果操作已經(jīng)存在,就不再重復執(zhí)行)
[_operationCache setObject:downLoadImageOperation forKey:model.icon];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 68;
}
#pragma mark -- UITableViewDataDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(@"----->ImageCacheCounts:%zd ----> OperationCacheCounts:%zd ---->CurrentOperationCounts:%zd",_imageCache.count,_operationCache.count,_queue.operationCount);
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
- (void)didReceiveMemoryWarning{
// [super didReceiveMemoryWarning];
[_imageCache removeAllObjects];
}
@end