当前位置: 移动技术网 > IT编程>移动开发>IOS > iOS开发多图下载程序浅析

iOS开发多图下载程序浅析

2017年12月18日  | 移动技术网IT编程  | 我要评论

ca1431,南丰县紫霄镇,漏罪

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

效果图如下:

\

打印效果:

\

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

 

涉及到的知识点:

 

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

 

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

比如:

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

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

3. 图片不会自动刷新

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

5. 数据错乱现象.

 

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

 

storyboard

\

 

 

\

 


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 uicollectionview实现横向滚动

    现在使用卡片效果的app很多,之前公司让实现一种卡片效果,就写了一篇关于实现卡片的文章。文章最后附有demo实现上我选择了使用uicollectionview ... [阅读全文]
  • iOS UICollectionView实现横向滑动

    本文实例为大家分享了ios uicollectionview实现横向滑动的具体代码,供大家参考,具体内容如下uicollectionview的横向滚动,目前我使... [阅读全文]
  • iOS13适配深色模式(Dark Mode)的实现

    iOS13适配深色模式(Dark Mode)的实现

    好像大概也许是一年前, mac os系统发布了深色模式外观, 看着挺刺激, 时至今日用着也还挺爽的终于, 随着iphone11等新手机的发售, ios 13系统... [阅读全文]
  • ios 使用xcode11 新建项目工程的步骤详解

    ios 使用xcode11 新建项目工程的步骤详解

    xcode11新建项目工程,新增了scenedelegate这个类,转而将原appdelegate负责的对ui生命周期的处理担子接了过来。故此可以理解为:ios... [阅读全文]
  • iOS实现转盘效果

    本文实例为大家分享了ios实现转盘效果的具体代码,供大家参考,具体内容如下demo下载地址: ios转盘效果功能:实现了常用的ios转盘效果,轮盘抽奖效果的实现... [阅读全文]
  • iOS开发实现转盘功能

    本文实例为大家分享了ios实现转盘功能的具体代码,供大家参考,具体内容如下今天给同学们讲解一下一个转盘选号的功能,直接上代码直接看viewcontroller#... [阅读全文]
  • iOS实现轮盘动态效果

    本文实例为大家分享了ios实现轮盘动态效果的具体代码,供大家参考,具体内容如下一个常用的绘图,主要用来打分之类的动画,效果如下。主要是ios的绘图和动画,本来想... [阅读全文]
  • iOS实现九宫格连线手势解锁

    本文实例为大家分享了ios实现九宫格连线手势解锁的具体代码,供大家参考,具体内容如下demo下载地址:效果图:核心代码://// clockview.m// 手... [阅读全文]
  • iOS实现卡片堆叠效果

    本文实例为大家分享了ios实现卡片堆叠效果的具体代码,供大家参考,具体内容如下如图,这就是最终效果。去年安卓5.0发布的时候,当我看到安卓全新的material... [阅读全文]
  • iOS利用余弦函数实现卡片浏览工具

    iOS利用余弦函数实现卡片浏览工具

    本文实例为大家分享了ios利用余弦函数实现卡片浏览工具的具体代码,供大家参考,具体内容如下一、实现效果通过拖拽屏幕实现卡片移动,左右两侧的卡片随着拖动变小,中间... [阅读全文]
验证码:
移动技术网