ios框架iOS开发代码段iOS开发

SDWebImage下载图片的底层实现

2016-03-23  本文已影响842人  Alexander
@WilliamAlex大叔

前言

SDWebImage框架是我们最常用的框架,我们下载图片,清除缓存等都可以使用该框架.下面我们模仿SDWebImage来实现多图片的下载

在写代码之前,我们先整理整理思路,下载图片我们分为两种情况,有缓存和没缓存,下面我们来看看我做的两幅图

有沙盒缓存.png

代码实现

在正式写代码之前,需要说明几步操作

#import "ViewController.h"
#import "WGApps.h"

@interface ViewController ()

/** 图片缓存(plist文件中是字典存储的) */
@property (nonatomic, weak) NSMutableDictionary *images;

/** apps数据源*/
@property(nonatomic, strong) NSArray *apps;

@end

@implementation ViewController

#pragma mark - 生命周期方法
- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.rowHeight = 44;
}


#pragma mark ----------------
#pragma mark - lazyLoading

- (NSMutableDictionary *)images {
    if (_images == nil) {

        _images = [NSMutableDictionary dictionary];
    }

    return _images;
}

- (NSArray *)apps {
    if (_apps == nil) {

        // 加载plist文件
        NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];

        // 加载数据
        NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];

        NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:dictArray.count];
        // 字典数组转模型数组
        for (NSDictionary *dict in dictArray) {

         [tempArray addObject:[WGApps appsWithDict:dict]];
        }
        _apps = tempArray;
    }
    return _apps;
}

#pragma mark - 数据源方法

// 一共有多少个cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

// 每一个cell显示什么内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    // 定义ID(最好和storyboard中定义的ID一致)
    static NSString *ID = @"apps";

    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // 通过模型拿到对应的资源
    WGApps *app = self.apps[indexPath.row];

    // 设置数据
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 下载头像

    // 1, 首先去内存缓存中取,如果没有再去沙盒中去
    UIImage *image = [self.images objectForKey:app.icon];

    if (image) {
        // 来到这里,说明图片缓存中已经有需要的图片,直接显示到对应的cell即可
        cell.imageView.image = image;
    } else
    {
        // 来到这里表示:图片缓存中没有所需要的图片,那么这时候就要到对应的沙盒中找有没有下载的图片

        // 1, 获取沙盒路径
        NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 注意:拿到沙盒路径还不够,我们需要的是下载图片的路径,所以需要拼接,拿到全路径
        // 2, 获取模型中图片资源最后一个目录名
        NSString *fileName = [app.icon lastPathComponent];

        // 3, 拼接路径
        NSString *fullPath = [cachesPath stringByAppendingPathComponent:fileName];

        // 注意 : 在沙盒中的保存的资源是以二进制的形式存在的.我们还需要判断沙盒中是否有下载过的图片
        // 4, 通过图片路径,拿到下载的图片
        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        // 5, 判断沙盒中是否有值
        if (imageData) {
            // 来到这里说明沙盒中有下载好的图片,直接将二进制转为图片显示即可,但是最后还需要讲图片报讯到图片缓存中,方便下次直接获取.
            UIImage *image = [UIImage imageWithData:imageData];

            // 显示图片
            cell.imageView.image = image;

            // 将图片保存到图片缓存中
            [self.images setObject:image forKey:app.icon];
        } else
        {

            // 来到这里说明沙盒中没有值,这时候我们就需要下载图片资源啦.
            // 下载图片
            NSURL *url = [NSURL URLWithString:app.icon];

            // 将url转为data保存到本地
            NSData *data = [NSData dataWithContentsOfURL:url];

            // 再将二进制转为图片显示到cell上
            UIImage *image = [UIImage imageWithData:data];

            // 显示图片
            cell.imageView.image = image;

            // 将下载的图片保存到图片缓存和沙盒中
            [self.images setObject:image forKey:app.icon];
            [data writeToFile:fullPath atomically:YES];

        }
    }
    return cell;
}

多图片下载(优化后的代码)


#import "ViewController.h"
#import "WGApps.h"


@interface ViewController ()

/** 图片缓存(plist文件中是字典存储的) */
@property (nonatomic, weak) NSMutableDictionary *images;

/** apps数据源*/
@property(nonatomic, strong) NSArray *apps;

/** 下载操作 */
@property(nonatomic, strong) NSMutableDictionary *operations;

/** 队列 */
@property(nonatomic, strong) NSOperationQueue *queue;

@end

@implementation ViewController

#pragma mark - 生命周期方法
- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.rowHeight = 44;
}


#pragma mark ----------------
#pragma mark - lazyLoading

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

- (NSOperationQueue *)queue {
    if (_queue == nil) {
        // 创建队列
        _queue = [[NSOperationQueue alloc] init];

        // 设置最大并发数
        _queue.maxConcurrentOperationCount = 3;
    }
    return _queue;
}

- (NSMutableDictionary *)images {
    if (_images == nil) {

        _images = [NSMutableDictionary dictionary];
    }

    return _images;
}

- (NSArray *)apps {
    if (_apps == nil) {

        // 加载plist文件
        NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];

        // 加载数据
        NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];

        NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:dictArray.count];
        // 字典数组转模型数组
        for (NSDictionary *dict in dictArray) {

         [tempArray addObject:[WGApps appsWithDict:dict]];
        }
        _apps = tempArray;
    }
    return _apps;
}

#pragma mark - 数据源方法

// 一共有多少个cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

// 每一个cell显示什么内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    // 定义ID(最好和storyboard中定义的ID一致)
    static NSString *ID = @"apps";

    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // 通过模型拿到对应的资源
    WGApps *app = self.apps[indexPath.row];

    // 设置数据
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 下载头像

    // 1, 首先去内存缓存中取,如果没有再去沙盒中去
    UIImage *image = [self.images objectForKey:app.icon];

    if (image) {
        // 来到这里,说明图片缓存中已经有需要的图片,直接显示到对应的cell即可
        cell.imageView.image = image;
    } else
    {
        // 来到这里表示:图片缓存中没有所需要的图片,那么这时候就要到对应的沙盒中找有没有下载的图片

        // 1, 获取沙盒路径
        NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 注意:拿到沙盒路径还不够,我们需要的是下载图片的路径,所以需要拼接,拿到全路径
        // 2, 获取模型中图片资源最后一个目录名
        NSString *fileName = [app.icon lastPathComponent];

        // 3, 拼接路径
        NSString *fullPath = [cachesPath stringByAppendingPathComponent:fileName];

        // 注意 : 在沙盒中的保存的资源是以二进制的形式存在的.我们还需要判断沙盒中是否有下载过的图片
        // 4, 通过图片路径,拿到下载的图片
        NSData *imageData = [NSData dataWithContentsOfFile:fullPath];

        // 5, 判断沙盒中是否有值
        if (imageData) {
            // 来到这里说明沙盒中有下载好的图片,直接将二进制转为图片显示即可,但是最后还需要讲图片报讯到图片缓存中,方便下次直接获取.
            UIImage *image = [UIImage imageWithData:imageData];

            // 显示图片
            cell.imageView.image = image;

            // 将图片保存到图片缓存中
            [self.images setObject:image forKey:app.icon];
        } else
        {

            //设置展占位图片
            cell.imageView.image = [UIImage imageNamed:@"占位图片"];

            //查看该图片的下载操作是否存在
            NSBlockOperation *download = [self.operations objectForKey:app.icon];
            if (download == nil) {

                download = [NSBlockOperation blockOperationWithBlock:^{

                    // 下载图片
                    NSURL *url = [NSURL URLWithString:app.icon];

                    // 阻塞1秒
                    [NSThread sleepForTimeInterval:1.0];

                    // 将图片转为二进制保存到本地沙盒中
                    NSData *data = [NSData dataWithContentsOfURL:url];

                    // 将二进制转为图片显示
                    UIImage *image = [UIImage imageWithData:data];

                    // 如果没有图片,一定要讲下载操作从字典中移除
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }

                    //保存图片到内存缓存
                    [self.images setObject:image forKey:app.icon];

                    //保存图片到沙河缓存
                    [data writeToFile:fullPath atomically:YES];

                    //线程间通信
                    [[NSOperationQueue mainQueue]addOperationWithBlock:^{

                        //刷新cell
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                    }];
                    [self.operations removeObjectForKey:app.icon];
                }];

                //加入到操作缓存
                [self.operations setObject:download forKey:app.icon];

                //把操作添加到队列
                [self.queue addOperation:download];
            }
        }
    }
    return cell;
}

@end

我们使用框架来实现同样功能

#import "ViewController.h"
#import "WGApps.h"
#import "UIImageView+WebCache.h"


@interface ViewController ()

/** apps数据源*/
@property(nonatomic, strong) NSArray *apps;

@end

@implementation ViewController

#pragma mark - 生命周期方法
- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.rowHeight = 84;
}

#pragma mark ----------------
#pragma mark - lazyLoading

- (NSArray *)apps {
    if (_apps == nil) {

        // 加载plist文件
        NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];

        // 加载数据
        NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];

        NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:dictArray.count];
        // 字典数组转模型数组
        for (NSDictionary *dict in dictArray) {

         [tempArray addObject:[WGApps appsWithDict:dict]];
        }
        _apps = tempArray;
    }
    return _apps;
}

#pragma mark - 数据源方法

// 一共有多少个cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

// 每一个cell显示什么内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    // 定义ID(最好和storyboard中定义的ID一致)
    static NSString *ID = @"apps";

    // 创建cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // 通过模型拿到对应的资源
    WGApps *app = self.apps[indexPath.row];

    // 设置数据
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;

    // 下载图片
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placehoder"]];

    return cell;
}

@end

总结

上一篇下一篇

猜你喜欢

热点阅读