// LZAppItem.h
#import <Foundation/Foundation.h>
@interface LZAppItem : NSObject
/** 名稱*/
@property (nonatomic ,strong) NSString *name;
/** 圖標(biāo)的地址*/
@property (nonatomic ,strong) NSString *icon;
/** 下載量*/
@property (nonatomic ,strong) NSString *download;
+(instancetype)appItemWithDict:(NSDictionary *)dict;
@end
// LZAppItem.m
#import "LZAppItem.h"
@implementation LZAppItem
+ (instancetype)appItemWithDict:(NSDictionary *)dict
{
// 創(chuàng)建對象
LZAppItem *appItem = [[LZAppItem alloc] init];
// KVC
[appItem setValuesForKeysWithDictionary:dict];
// 返回對象
return appItem;
}
@end
// ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UITableViewController
@end
// ViewController.m
#import "ViewController.h"
#import "LZAppItem.h"
@interface ViewController ()
/** 隊列*/
@property (nonatomic ,strong) NSOperationQueue *queue;
/** 模型數(shù)組*/
@property (nonatomic, strong) NSArray *apps;
/** 圖片緩存*/
@property (nonatomic ,strong) NSMutableDictionary *images;
/** 操作*/
@property (nonatomic ,strong) NSMutableDictionary *operations;
@end
@implementation ViewController
#pragma mark - 懶加載數(shù)據(jù)
- (NSArray *)apps
{
if (_apps == nil) {
// 1獲取字典數(shù)組
// 1.1獲取路徑
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
// 1.2獲取到字典數(shù)組
NSArray *dictArray = [NSArray arrayWithContentsOfFile:filePath];
// 2.拿到模型數(shù)組
// 2.1創(chuàng)建一個可變數(shù)組
NSMutableArray *temp = [NSMutableArray array];
// 2.2創(chuàng)建for循環(huán)
for (NSDictionary *dict in dictArray) {
LZAppItem *item = [LZAppItem appItemWithDict:dict];
[temp addObject:item];
}
_apps = temp;
}
return _apps;
}
#pragma mark - 圖片緩存
- (NSMutableDictionary *)images
{
if (_images == nil) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
#pragma mark - 隊列
- (NSOperationQueue *)queue
{
if (_queue == nil) {
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
#pragma mark - UITableViewDataSource方法
// 返回多少組
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// 每組返回多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
// 每行顯示什么內(nèi)容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app";
// 去緩存池里面找
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
// 拿到模型數(shù)據(jù)
LZAppItem *item = self.apps[indexPath.row];
// 賦值
cell.textLabel.text = item.name;
cell.detailTextLabel.text = item.download;
// 賦值圖片握侧,重點(diǎn)
// 檢查緩存,用圖片的地址做鍵
UIImage *image = [self.images objectForKey:item.icon];
if (image) { // 有內(nèi)存緩存打却,即身上有錢
// 直接賦值
cell.imageView.image = image;
// NSLog(@"%zd使用了內(nèi)存緩存",indexPath.row);
}else { // 沒有內(nèi)存緩存熊楼,即身上沒有錢
// 獲取磁盤緩存路徑
// 0.0獲取路徑
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 0.1得到圖片的名稱
NSString *fileName = [item.icon lastPathComponent];
// 0.2拼接文件的路徑
NSString *fullPath = [cachePath stringByAppendingPathComponent:fileName];
// 去檢查磁盤緩存,這里找到的是保存在cache里面的NSData數(shù)據(jù)
NSData *imgData = [NSData dataWithContentsOfFile:fullPath];
if (imgData) { // 如果磁盤緩存里面有該圖片辫狼,磁盤緩存即是卡里面有錢
// 拿到Image
UIImage *image = [UIImage imageWithData:imgData];
cell.imageView.image = image;
// 把圖片保存到身上厘熟,內(nèi)存緩存
[self.images setObject:image forKey:item.icon];
// NSLog(@"%zd使用了磁盤緩存",indexPath.row);
} else { // 如果磁盤緩存里面沒有該圖片,磁盤緩存即是卡里面沒有錢
// 來到這里蘸吓,說明又沒有內(nèi)存緩存车海,又沒有磁盤緩存
// 清空圖片或者是設(shè)置占位圖片枢舶,目的是什么赶么,因為cell是重復(fù)利用的肩豁,假設(shè)當(dāng)你第一張圖片顯示完畢的時候,用戶繼續(xù)往下拖拽辫呻,下面的cell是由上面消失的cell重復(fù)利用過來的清钥,而下面的cell去下載圖片的時間可能比較長,所以顯示的效果是上一張殘留下來的圖片放闺,之后再把從網(wǎng)絡(luò)下載的圖片進(jìn)行覆蓋祟昭,也就是圖片錯亂了,所以怖侦,為了防止這個問題篡悟,用一張占位圖片解決
cell.imageView.image = [UIImage imageNamed:@"Snip20200808_172"];
/*避免重復(fù)操作,當(dāng)程序第一次運(yùn)行起來的時候匾寝,顯示第一個cell的時候搬葬,創(chuàng)建一個操作去服務(wù)器端獲取數(shù)據(jù),然后用戶又隨便往下拖拽艳悔,那么第一個cell就不再顯示了急凰,此時那個獲取數(shù)據(jù)的操作還在執(zhí)行,假設(shè)需要10秒猜年,然后用戶又隨便拖拽抡锈,滾到了第一個cell,第一個cell又重新顯示出來了码倦,那么它還會繼續(xù)創(chuàng)建一個操作去服務(wù)器端獲取數(shù)據(jù)企孩,那么可能就有好幾個操作發(fā)送到服務(wù)器端去獲取同一個數(shù)據(jù)了,沒有必要袁稽,所以勿璃,這里采用了一個可變字典,用來判斷,好辦法补疑,誰想出來的歧沪,牛逼*/
// 檢查操作緩存
NSBlockOperation *dowbloadOperation = [self.operations objectForKey:item.icon];
if (dowbloadOperation) { // 如果有操作,說明之前已經(jīng)發(fā)了一次操作過去了莲组,那么再次來到這的時候诊胞,就不能再發(fā)請求了,所以什么也不能做
} else { // 如果沒有操作锹杈,說明之前沒有發(fā)過操作過去撵孤,要添加操作
dowbloadOperation = [NSBlockOperation blockOperationWithBlock:^{
// 1.創(chuàng)建url
NSURL *url = [NSURL URLWithString:item.icon];
// 2.拿到二進(jìn)制數(shù)據(jù)
// 該方法通過url獲取數(shù)據(jù)是有時間限制的,30秒,如果失敗竭望,返回nil
NSData *data = [NSData dataWithContentsOfURL:url];
// 3.轉(zhuǎn)化為UIImage對象
UIImage *image = [UIImage imageWithData:data];
// 4.判斷
if (image == nil) {
// 把這次操作刪除掉
[self.operations removeObjectForKey:item.icon];
return ;
}
// 5.保存到內(nèi)存緩存
[self.images setValue:image forKey:item.icon];
// 6.保存到磁盤緩存
[data writeToFile:fullPath atomically:YES];
NSLog(@"%zd直接下載",indexPath.row);
// 設(shè)置圖片
// 下面這行代碼應(yīng)該放在主線程,那么應(yīng)該回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 下面這個block塊里面的代碼是在主線程執(zhí)行的
// 如果沒有刷新操作邪码,那么不會顯示圖片,為什么咬清,因為你雖然賦值了闭专,但是沒有
// 調(diào)用它的layoutsubviews創(chuàng)建尺寸,只有調(diào)用layoutsubviews才會顯示出圖片
// 也就是刷新,或者點(diǎn)擊某一行的時候旧烧,它會觸發(fā)layoutsubviews方法影钉,那么才會
// 顯示出圖片了,但是掘剪,你用reloadData又沒有必要平委,只需要設(shè)置某一張圖片,你刷新
// 整個可視區(qū)域杖小,那就傻逼了肆汹,所以愚墓,這里面最好的方式是予权,刷新特定的行數(shù)
// cell.imageView.image = image;
// [tableView reloadData];
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
}];
}];
// 添加操作到內(nèi)存緩存中
[self.operations setObject:dowbloadOperation forKey:item.icon];
// 添加到隊列
[self.queue addOperation:dowbloadOperation];
}
}
}
// 返回cell
return cell;
}
- (void)didReceiveMemoryWarning
{
// 移除內(nèi)存緩存
[self.images removeAllObjects];
// 取消隊列中的操作
[self.queue cancelAllOperations];
}
@end
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者