iOS开发沙盒缓存

释放双眼,带上耳机,听听看~!

内存指的就是主板上的存储部件,是CPU直接与之沟通,并用其存储数据的部件,存放当前正在使用的(即执行中)的数据和程序,它的物理实质就是一组或多组具备数据输入输出和数据存储功能的集成电路,内存只用于暂时存放程序和数据,一旦关闭电源或发生断电,其中的程序和数据就会丢失。

#import "ViewController.h"
#import "CZApp.h"
@interface ViewController ()
// plist文件数据的容器
@property (nonatomic, strong) NSArray *appList;

// 管理下载的全局队列
@property (nonatomic, strong) NSOperationQueue *opQueue;

/**所有下载的缓冲池*/
@property (nonatomic, strong) NSMutableDictionary *operationCache;

/**保存所有图像的缓存*/
@property (nonatomic, strong) NSMutableDictionary *imageCache;
@end


@implementation ViewController

// 懒加载
-(NSArray *)appList
{
    if (_appList == nil) {
        NSArray *dicArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        // 字典转模型
        NSMutableArray *arryM = [NSMutableArray array];
        for(NSDictionary *dict in dicArray){
            CZApp *app = [CZApp appWithDict:dict];
            [arryM addObject:app];
        }
        _appList = arryM;
    }
    return _appList;
}

-(NSOperationQueue *)opQueue
{
    if (_opQueue == nil) {
        _opQueue = [[NSOperationQueue alloc] init];
    }
    return _opQueue;
}

-(NSMutableDictionary *)operationCache
{
    if (_operationCache == nil) {
        _operationCache = [[NSMutableDictionary alloc] init];
    }
    return _operationCache;
}

-(NSMutableDictionary *)imageCache
{
    if (_imageCache == nil) {
        _imageCache = [[NSMutableDictionary alloc] init];
    }
    return _imageCache;
}

#pragma mark - 实现数据源方法
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.appList.count;
}

/**
  - 重构代码,便于维护问题1:如果网速比较慢,会很卡
 解决方法:使用异步下载

 问题2:图片没有Frame,所有cell初始化的时候,给imageView的frame是0。异步下载完之后不显示  解决办法:使用占位图(如果展位图比较大, 自定义cell可以解决)

 问题3:如果图片下载速度不一致,同时用户快速滚动的时候,会因为Cell的重用导致图片混乱
 解决方法:MVC,使用Model(模型)保存下载的图像,再次刷新表格。

 问题4:在用户快速滚动的时候,会重复添加下载任务到下载队列。
 解决方法:建立下载操作的缓冲池。首先检查缓冲池里是否有当前图片的下载操作。有的话就不创建下载操作。从而保证一张图片只添加一个下载操作。其实就是建立一个全局的字典属性。

 问题5: 将图片保存到模型里的优缺点
 优点:不用重复下载,利用MVC刷新表格,不会造成数据混乱,加载速度比较快
 缺点:内存,所有下载好的图像都会记录在模型里。如果数据比较多(2000)个就会造成内存警告。

 -***图像根模型耦合性太强。导致清理内存非常困难
 解决办法:模型跟图像分开。在控制器里做缓存。
 问题6:下载操作缓冲池会越来越大。需要及时清理。

 */
/**
 代码重构:1.如果代码太长。
 目的:
 - 写的时候,如果思路清楚,能够一次性写完,但是也要注意同构。
 - 时间长了,不好阅读


 重构方法:
 如果有一部分代码专门解决某一问题,就封装起来。
 1. 新建一个方法—> 剪切代码。
 2. 传参数。
 3. 在原来剪切代码的地方,调用抽取的方法。
 4. 注意,测试。
 5. 注意if嵌套,在实际的开发,非常忌讳很深的嵌套。
 */

-(void)viewDidLoad
{
    NSLog(@"%@", [self cachePathWithUrl:@""]);
}

// cell里面的imageView子控件是懒加载
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"AppCell";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
    // 给Cell设置数据
    CZApp *app = self.appList[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 判断模型里面是否有图像
    if ([self.imageCache objectForKey:app.icon]) { // 内存有图片
        NSLog(@" 图片已经下载......");
        cell.imageView.image = self.imageCache[app.icon];
    }else{
        // 内存无图片
        // 显示图片
        // 如果沙盒里面有图片,直接从沙盒加载
        UIImage *image = [UIImage imageWithContentsOfFile:[self cachePathWithUrl:app.icon]];
        if (image) {   //  沙盒有图片
            NSLog(@"直接从沙盒加载图片");
            // 1. 设置图片缓存到内存,方便下次从内存直接加载
            [self.imageCache setObject:image forKey:app.icon];

            // 2. 显示图片到cell
            cell.imageView.image = self.imageCache[app.icon];
        }else{  // 沙盒没有图片

            // 显示图片—占位图
            cell.imageView.image = [UIImage imageNamed:@"user_default"];
//#warning mark-从这里开始剪切的代码

            // 下载图片
            [self downloadImage:indexPath];
        }
    }
    return cell;
}

-(void)downloadImage:(NSIndexPath *)indexPath
{
    CZApp *app = self.appList[indexPath.row];
    /**
     如果下载缓冲池里面有当前图片的下载操作,就不用创建下载操作,没有才创建
     缓冲池字典中 key:存放当前图片的url,字符串类型。
     Value:保存下载操作
     */
    if (self.operationCache[app.icon])
    {
        NSLog(@"正在玩命下载中......");
        return;
    }
    // 缓冲池没有下载操作

    // 异步下载图片
    __weak typeof(self) weakSelf = self;
    NSBlockOperation  *downLoadOp = [NSBlockOperation blockOperationWithBlock:^{
        // 模拟延时
        [NSThread sleepForTimeInterval:2];
        NSLog(@"正在下载中......");

        //  1. 下载图片(二进制数据)
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
        NSLog(@"%@", data);
        UIImage *image = [UIImage imageWithData:data];

        //  2. 将下载的数据保存到沙盒
        // 字典的赋值不能为nil,赋值为nil会崩溃
        if (image) {
            // 先保存到图片缓存
            [weakSelf.imageCache setObject:image forKey:app.icon];

            // 将图片写入到沙盒
            [data writeToFile:[self cachePathWithUrl:app.icon] atomically:YES];
        }

        // 3 将操作从缓冲池删除——将下载好的图片操作从缓冲池中移除
        [weakSelf.operationCache removeObjectForKey:app.icon];

        //  4. 在主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            /** reload 会重新调用cell的初始化方法, 会重新判断模型里面是否有图像
             有的话就直接显示
             */
        }];
    }];

    // 将操作添加到队列
    [weakSelf.opQueue addOperation:downLoadOp];
    NSLog(@"操作的数量------------->%ld", self.opQueue.operationCount);

    // 将操作添加到缓冲池中(使用图片的url作为key)
    [weakSelf.operationCache setObject:downLoadOp forKey:app.icon];
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{   // 点击后查看操作缓冲池内的操作详情
    NSLog(@"%@", self.operationCache);
}

/**
 在真实开发中,一定要注意这个方法
 */
-(void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    // 需要在这里做一些内存清理的工作,如果不处理会被系统强制闪退
    // 清理内存
    [self.imageCache  removeAllObjects];

    // 清理操作的缓存
    [self.operationCache removeAllObjects];

    // 取消下载队列内的任务
    [self.opQueue cancelAllOperations];
}

/**
 拼接一个文件在沙盒的全路径
 */
-(NSString *)cachePathWithUrl:(NSString *)urlStr
{  // 将图片网址名作为作为最后一项
    // 1. 获得缓存的路径
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

    // 2. 把路径根urlStr拼接起来
    return [cachePath stringByAppendingPathComponent:urlStr.lastPathComponent];
}
-(void)dealloc
{
    NSLog(@"销毁控制器-------------");
}
@end

有沙盒路径对我们比较重要,因为他能够让我们在断网且退出程序后,在进入程序任然能够使用。当然我们也必须限制沙盒中存入数据的个数,最好能让其更新。这样我们就无需担心没有网时只能看见很久以前的消息。

 

为TA充电
共{{data.count}}人
人已赞赏
iOS文章

iOS开发NSOperation与GCD之间的关系

2021-6-3 14:29:20

iOS文章

IOS开发缓存之NSCache缓存

2021-6-3 14:35:38

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索