欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS开发多图下载程序浅析

程序员文章站 2022-03-08 15:19:08
iOS开发多图下载程序浅析。 效果图如下: 打印效果: 上图打印效果,展现了滚动tableView重复从网络中下载数据的现象,在后面会对上面打印做介绍.  ...

iOS开发多图下载程序浅析。

效果图如下:

iOS开发多图下载程序浅析

打印效果:

iOS开发多图下载程序浅析

上图打印效果,展现了滚动tableView重复从网络中下载数据的现象,在后面会对上面打印做介绍.

 

涉及到的知识点:

 

     01 字典转模型
     02 存储数据到沙盒,从沙盒中加载数据
     03 占位图片的设置(cell的刷新问题)
     04 如何进行内存缓存(使用NSDictionary)
     05 在程序开发过程中的一些容错处理
     06 如何刷新tableView的指定行(解决数据错乱问题)
     07 NSOperation以及线程间通信相关知识

 

看效果图,感觉很简单,创建一个UITableView,在cell上面设置数据. 以前在都是一些现成的数据,这次试用的数据(图片)是通过URL从网络中下载来的,因此会出现很多问题!

比如:

1. UI很不流畅 --------> 开子线程下载图片

2. 图片重复从网络中下载--------> 把下载过的图片保存起来

3. 图片不会自动刷新

4. 当网络延迟时,图片又会重复下载

5. 数据错乱现象.

 

首先不考虑上面出现的问题,先把上面的效果图做好.然后再根据上面问题逐一解决.

 

storyboard

iOS开发多图下载程序浅析

 

 

iOS开发多图下载程序浅析

 


ZYTableViewController文件

这个tableViewController和storyboard中的控制器是绑定好的.

 

//
//  ZYTableViewController.h
//  00-掌握-多图下载综合案例-数据展示
//
//  Created by 朝阳 on 2017/11/22.
//  Copyright © 2017年 sunny. All rights reserved.
//

#import 

@interface ZYTableViewController : UITableViewController

@end

#import "ZYTableViewController.h"
#import "ZYApps.h"

@interface ZYTableViewController ()

@property (nonatomic, strong) NSArray *apps;

@end

@implementation ZYTableViewController

#pragma -mark lazy loading
- (NSArray *)apps
{
    if (!_apps) {
        // 加载plist文件
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        // 创建一个临时可变数组
        NSMutableArray *tempArray = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            [tempArray addObject:[ZYApps appWithDict:dict]];
        }
        _apps = tempArray;
    }
    return _apps;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.apps.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *ID = @"app";
    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
    
    // 设置数据给cell
    ZYApps *app = self.apps[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    
    NSURL *url = [NSURL URLWithString:app.icon];
    NSData *iconData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:iconData];
    cell.imageView.image = image;
    
//    NSLog(@"%ld----",indexPath.row);

/*
 存在两个严重问题:
 1. UI很不流畅 ----> 开子线程下载图片
 2. 图片重复下载 ----> 先把之前已经下载的图片保存起来
 
 */
    // 返回cell
    return cell;
}


@end

模型数据ZYApps文件

 

 

//
//  ZYApps.h
//  00-掌握-多图下载综合案例-数据展示
//
//  Created by 朝阳 on 2017/11/22.
//  Copyright © 2017年 sunny. All rights reserved.
//

#import 

@interface ZYApps : NSObject

/** app名称 */
@property(nonatomic, strong) NSString * name;
/** app下载量 */
@property(nonatomic, strong) NSString * download;
/** app图标 */
@property(nonatomic, strong) NSString * icon;

+ (instancetype)appWithDict:(NSDictionary *)dict;

@end

@implementation ZYApps

+ (instancetype)appWithDict:(NSDictionary *)dict
{
    ZYApps *apps = [[ZYApps alloc] init];
    // 利用KVC
    [apps setValuesForKeysWithDictionary:dict];
    return apps;
}

@end

 

 

 

以上代码就可以实现效果图.但是存在两个严重的问题:

1. 图片被重复下载

2. UI很不流畅

问题1. 图片被重复下载

因为当滚动tableView的时候,会重复下载网络中的图片.----解决---> 先把下载好的图片保存起来

具体解决:

当应用程序第一次下载下来的时候,tableView中的图片,需要从网络中下载下来.然后把图片保存到内存缓存一份,把图片也写入到沙盒中一份.

当来回滚动tableView的时候,下载过的图片已经在内存中缓存过了,因此获取内存中的图片就可以了.由此防止了重复下载图片的现象.把图片的二进制写入到沙盒中,原因是

因为当应用程序重新启动的时候,在应用程序内存中缓存的图片都清空了,因此还需要重新从网络上下载图片,保存到沙盒中就是为了当重新启动应用程序的时候,数据可以从沙盒中读取,防止重复下载.

 

此时 tableView:cellForRowAtIndexPath:方法中代码.

 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *ID = @"app";
    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
    
    // 设置数据给cell
    ZYApps *app = self.apps[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    
    // 设置图标
    // 查看内存缓存中该图片是否存在,若存在直接用,否则去磁盘缓存中查看是否有缓存\
    如果有磁盘缓存,就保存一份到内存.设置图片,否则下载
    
    // 从内存缓存中读取
    UIImage *image = [self.images objectForKey:app.icon];
    // 是否内存中存在已下载的图片
    if (image) {
        cell.imageView.image = image;
        NSLog(@"使用内存缓存中的图片---%ld",indexPath.row);
    }else{
        // 保存图片到沙盒缓存
        /*
         arg1: 沙盒的哪个目录
         arg2: 去主目录下去搜索,默认就是NSUserDomainMask
         arg3: 是否展开路径
         */
        NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 获得图片名称,不能包含/
        NSString *fileName = [app.icon lastPathComponent];
        // 拼接图片的全路径
        NSString *fullPath = [caches stringByAppendingPathComponent:fileName];
        
        // 检查磁盘缓存
        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
        if (imageData) {
            UIImage *image = [UIImage imageWithData:imageData];
            // 设置图标
            cell.imageView.image = image;
            NSLog(@"%ld--使用了磁盘缓存的图片--",indexPath.row);
            // 把图片保存到内存中一份
            [self.images setObject:image forKey:app.icon];
//            NSLog(@"%@",fullPath);
            
        }else{
            
            NSURL *url = [NSURL URLWithString:app.icon];
            NSData *iconData = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:iconData];
            cell.imageView.image = image;
            // 把图片保存到内存缓存
            [self.images setObject:image forKey:app.icon];
            // 写数据到沙盒
            [iconData writeToFile:fullPath atomically:YES];
//            NSLog(@"%@",fullPath);
            NSLog(@"%ld--下载--",indexPath.row);
        }
    }
    
    /*
     存在两个严重问题:
     1. UI很不流畅 ----> 开子线程下载图片
     2. 图片重复下载 ----> 先把之前已经下载的图片保存起来
     */
    
    // 返回cell
    return cell;
}

这样就解决了重复下载图片.

 

 

解决上面的5个问题 和 ZYTableViewController文件

 

问题:

1. UI很不流畅 --->开子线程下载图片

2. 图片重复下载 ---> 先把之前已经下载的图片保存起来(字典)

内存缓存 ---> 磁盘缓存

 

3. 图片不会自动刷新:

原因: 因为cell是subTitle类型的,subTitle类型中的image默认frame是(0,0,0,0)的,当显示cell的时候,image的frame还是(0,0,0,0),此时有图片已经下载完了.因为是开子线程下载图片的,程序是异步的,因此先return cell,此时的cell的Image的frame为0,图片设置上去也是不显示的.

解决: 手动刷新每一行cell. reloadRowsAtIndexPaths:withRowAnimation:,这个方法会调用cellForRow方法,因此会重新创建cell,cell的Image此时已经在内存缓存了.

 

4.(当网络延迟时)图片重复下载:

因为当下载一个cell的图片时候需要2s,当这个cell下载到1s的时候,用户滚动速度较快,此时整个cell被存放到缓存池中了(此时cell的图片还没有下载完),当下一个cell显示的时候,会从缓存池中取,此时缓存池中没有下载好图片的cell. 因此会出现重复下载现象

 

5. 数据错乱

原因: cell的复用问题造成的,当从缓存池中复用cell的同时,把复用的cell的图片也复用过来了.因此出现数据紊乱现象

解决: 当cell需要下载新的图片之前,清空cell原来的图片(设置占位图片)

 

 

#import "ZYTableViewController.h"
#import "ZYApps.h"

@interface ZYTableViewController ()
/** 模型数组 */
@property (nonatomic, strong) NSArray *apps;
/** 存放下载过的图片 */
@property (nonatomic,strong) NSMutableDictionary *images;
/** 队列 */
@property (nonatomic,strong) NSOperationQueue *queue;
/** 操作缓存 */
@property (nonatomic,strong) NSMutableDictionary *operations;

@end

@implementation ZYTableViewController

#pragma -mark lazy loading
- (NSArray *)apps
{
    if (!_apps) {
        // 加载plist文件
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        // 创建一个临时可变数组
        NSMutableArray *tempArray = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            [tempArray addObject:[ZYApps appWithDict:dict]];
        }
        _apps = tempArray;
    }
    return _apps;
}

- (NSMutableDictionary *)images
{
    if (!_images) {
        _images = [NSMutableDictionary dictionary];
    }
    return _images;
}

- (NSOperationQueue *)queue
{
    if(!_queue){
        _queue = [[NSOperationQueue alloc] init];
        // 设置最大并发数:并行执行的任务数
        _queue.maxConcurrentOperationCount = 5;
    }
    return _queue;
}

- (NSMutableDictionary *)operations
{
    if (!_operations) {
        _operations = [NSMutableDictionary dictionary];
    }
    return _operations;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (void)didReceiveMemoryWarning
{
    // 当发生内存警告的时候
    [self.images removeAllObjects];
    // 取消队列中所有的操作
    [self.queue cancelAllOperations];
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
    return self.apps.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *ID = @"app";
    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
    
    // 设置数据给cell
    ZYApps *app = self.apps[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    
    // 设置图标
    // 查看内存缓存中该图片是否存在,若存在直接用,否则去磁盘缓存中查看是否有缓存\
    如果有磁盘缓存,就保存一份到内存.设置图片,否则下载
    
    // 从内存缓存中读取
    UIImage *image = [self.images objectForKey:app.icon];
    // 是否内存中存在已下载的图片
    if (image) {
        cell.imageView.image = image;
        NSLog(@"使用内存缓存中的图片---%ld",indexPath.row);
    }else{
        // 保存图片到沙盒缓存
        /*
         arg1: 沙盒的哪个目录
         arg2: 去主目录下去搜索,默认就是NSUserDomainMask
         arg3: 是否展开路径
         */
        NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 获得图片名称,不能包含/
        NSString *fileName = [app.icon lastPathComponent];
        // 拼接图片的全路径
        NSString *fullPath = [caches stringByAppendingPathComponent:fileName];
        
        // 检查磁盘缓存
        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
        // 废除
//        imageData = nil;
        if (imageData) {
            UIImage *image = [UIImage imageWithData:imageData];
            // 设置图标
            cell.imageView.image = image;
            NSLog(@"%ld--使用了磁盘缓存的图片--",indexPath.row);
            // 把图片保存到内存中一份
            [self.images setObject:image forKey:app.icon];
            //            NSLog(@"%@",fullPath);
            
        }else{
            
            // 创建队列(注意:在这里会创建很多个队列);
            //            NSOperationQueue *queue = [[NSOperationQueue alloc] init];
            
            //## 检查该图片是否正在下载,如果是那么就什么都不做,否则再添加下载任务
            NSBlockOperation *downloadImage = [self.operations objectForKey:app.icon];
            if (downloadImage) {
                // 什么都不做
            }else{
                
                // 清空cell之前的图片
//                cell.imageView.image = nil;
                // 占位图片
//                cell.imageView.image = [UIImage imageNamed:@"qq"];
                
                // 创建操作
                downloadImage = [NSBlockOperation blockOperationWithBlock:^{
                    
                    NSURL *url = [NSURL URLWithString:app.icon];
                    NSData *iconData = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:iconData];
                    
                    //NSLog(@"%ld--下载--",indexPath.row);
                    
                    // 容错处理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return;
                    }
                    
                    // 演示网络延迟
                    //[NSThread sleepForTimeInterval:2.0];
                    
                    // 线程间通信
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        
                        // cell.imageView.image = image;
                        // 刷新一行(会重新调用cellForRow方法)
                        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
                    }];
                    
                    // 把图片保存到内存缓存
                    [self.images setObject:image forKey:app.icon];
                    // 写数据到沙盒
                    [iconData writeToFile:fullPath atomically:YES];
                    //            NSLog(@"%@",fullPath);
                    NSLog(@"%ld--下载--",indexPath.row);
                    
                    // 移除图片的下载操作
                    [self.operations removeObjectForKey:app.icon];
                    
                }];
                
                // 添加操作到操作缓存中
                [self.operations setObject:downloadImage forKey:app.icon];
                
                // 把操作添加到队列中
                [self.queue addOperation:downloadImage];
                
            }  
        }
    }
    
    // 返回cell
    return cell;
}

@end

 

 

沙盒

Documents: 会备份,不允许

tmp: 临时路径(随时会被删除)

Libray

Preferences: 偏好设置,保存账号密码

caches: 缓存文件

 

iOS开发多图下载程序浅析