iOS开发基础38-多线程之多图片下载及缓存处理

在 iOS 开发中,处理图片的下载和缓存是一个常见需求。本文将详细介绍如何使用 NSOperationQueue 实现多图片下载,及其高级用法。同时,我们也会对比 SDWebImage 库,并分析其主要功能及底层逻辑。通过这种方式,帮助我们更高效地进行图片下载和缓存处理。

一、快速生成沙盒目录的路径

在 iOS 中,文件系统对应用提供了多个沙盒目录,每个目录有其独特的功能和用途。理解这些目录的功能和使用方法,可以帮助我们更好地管理文件和数据。

沙盒目录的各个文件夹功能

  1. Documents

    • 用途:保存由应用程序本身产生的文件或数据,例如:游戏进度、绘图数据等。
    • iCloud 备份:目录中的文件会被自动保存在 iCloud 上。
    • 注意:不要保存从网络上下载的文件,否则会导致应用无法上架。
  2. Caches

    • 用途:保存临时文件,"后续需要使用",例如:缓存图片,离线数据(地图数据)。
    • 清理:系统不会自动清理 Caches 目录中的文件,但开发者需要提供清理解决方案。
  3. tmp

    • 用途:保存临时文件,"后续不需要使用"。
    • 清理:tmp 目录中的文件系统会自动清理,重新启动设备时,该目录会被清空。当系统磁盘空间不足时,系统也会自动清理该目录。
  4. Preferences

    • 用途:保存用户偏好设置,使用 NSUserDefaults 直接读写。
    • 写入磁盘:如果需要数据及时写入磁盘,还需调用同步方法。

快速获取沙盒目录路径的工具类

为方便开发者快速获取沙盒目录路径,我们可以给 NSString 编写一个分类,提供获取 DocumentsCachestmp 目录路径的方法。

NSString+Path.h

#import <Foundation/Foundation.h>

@interface NSString (Path)

// 用于生成文件在 `Caches` 目录中的路径
- (instancetype)cacheDir;

// 用于生成文件在 `Documents` 目录中的路径
- (instancetype)docDir;

// 用于生成文件在 `tmp` 目录中的路径
- (instancetype)tmpDir;

@end

NSString+Path.m

#import "NSString+Path.h"

@implementation NSString (Path)

- (instancetype)cacheDir {
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    return [path stringByAppendingPathComponent:[self lastPathComponent]];
}

- (instancetype)docDir {
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    return [path stringByAppendingPathComponent:[self lastPathComponent]];
}

- (instancetype)tmpDir {
    NSString *path = NSTemporaryDirectory();
    return [path stringByAppendingPathComponent:[self lastPathComponent]];
}

@end

二、多图片下载

使用 NSOperationQueue 实现多图片下载

以下代码示例展示了如何利用 NSOperationQueue 进行多图片下载,并对下载的图片进行缓存(内存缓存和磁盘缓存)。

ViewController.m

#import "ViewController.h"
#import "XMGApp.h"
#import "NSString+Path.h"

@interface ViewController ()

@property (nonatomic, strong) NSArray *apps;
@property (nonatomic, strong) NSMutableDictionary *imageCaches;
@property (nonatomic, strong) NSMutableDictionary *operations;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView.rowHeight = 150;
}

#pragma mark - UITableViewDatasource

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
    XMGApp *app = self.apps[indexPath.row];
    
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download];
    cell.imageView.image = [UIImage imageNamed:@"placeholder"]; // 占位图
    
    UIImage *image = self.imageCaches[app.icon];
    if (image == nil) {
        NSString *filePath = [app.icon cacheDir];
        NSData *data = [NSData dataWithContentsOfFile:filePath];
        
        if (data == nil) {
            // 从网络下载图片
            NSOperationQueue *queue = [[NSOperationQueue alloc] init];
            NSBlockOperation *op = self.operations[app.icon];
            if (op == nil) {
                op = [NSBlockOperation blockOperationWithBlock:^{
                    NSURL *url = [NSURL URLWithString:app.icon];
                    data = [NSData dataWithContentsOfURL:url];
                    
                    if (data == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return;
                    }
                    
                    UIImage *image = [UIImage imageWithData:data];
                    self.imageCaches[app.icon] = image;
                    [data writeToFile:filePath atomically:YES];
                    
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                        [self.operations removeObjectForKey:app.icon];
                    }];
                }];
                
                self.operations[app.icon] = op;
                [queue addOperation:op];
            }
        } else {
            // 使用磁盘缓存中的图片
            UIImage *image = [UIImage imageWithData:data];
            self.imageCaches[app.icon] = image;
            cell.imageView.image = image;
        }
    } else {
        // 使用内存缓存中的图片
        cell.imageView.image = image;
    }
    
    return cell;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    self.imageCaches = nil;
    self.operations = nil;
    self.apps = nil;
}

#pragma mark - Lazy Loaders

- (NSArray *)apps {
    if (!_apps) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray *arr = [NSArray arrayWithContentsOfFile:path];
        NSMutableArray *models = [NSMutableArray arrayWithCapacity:arr.count];
        
        for (NSDictionary *dict in arr) {
            XMGApp *app = [XMGApp appWithDict:dict];
            [models addObject:app];
        }
        _apps = [models copy];
    }
    return _apps;
}

- (NSMutableDictionary *)imageCaches {
    if (!_imageCaches) {
        _imageCaches = [NSMutableDictionary dictionary];
    }
    return _imageCaches;
}

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

@end

XMGApp 模型类

#import <Foundation/Foundation.h>

@interface XMGApp : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *download;

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

@end

@implementation XMGApp

- (instancetype)initWithDict:(NSDictionary *)dict {
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

+ (instancetype)appWithDict:(NSDictionary *)dict {
    return [[self alloc] initWithDict:dict];
}

@end

三、SDWebImage 简介

SDWebImage 是一个强大的第三方库,用于处理图片下载、缓存及加载。以下是 SDWebImage 的主要功能及其实现原理:

1. 缓存机制

  • 内存缓存:利用 NSCache 缓存图片,提高图片加载速度。

    • 当接收到内存警告时,自动清空内存缓存。
  • 磁盘缓存:将图片保存在沙盒目录的 Caches 文件夹中的 default 子目录中。

    • 磁盘缓存时间:默认一周。超过缓存时间的数据会自动删除。
    • 磁盘清理:可以手动清除过期或所有缓存。

2. GIF 支持

SDWebImage 支持直接播放 GIF 图片。其加载过程如下:

  1. 下载 GIF 图片。
  2. 解析 GIF 图片,取出所有帧及其显示时间。
  3. 生成一个可动画的 UIImage 对象。

3. 图片类型识别

通过图片的前 8 个字节的十六进制数据进行图片类型识别,例如:

  • PNG
  • JPG
  • GIF

4. 使用 SDWebImage 简化图片下载流程

通过 SDWebImage,可以方便地实现图片下载、缓存和加载。

ViewController.m 改进

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

@interface ViewController ()

@property (nonatomic, strong) NSArray *apps;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView.rowHeight = 150;
}

#pragma mark - UITableViewDatasource

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
    XMGApp *app = self.apps[indexPath.row];
    
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download];
    
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placeholder"]];
    
    return cell;
}

#pragma mark - Lazy Loaders

- (NSArray *)apps {
    if (!_apps) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
        NSArray *arr = [NSArray arrayWithContentsOfFile:path];
        NSMutableArray *models = [NSMutableArray arrayWithCapacity:arr.count];
        
        for (NSDictionary *dict in arr) {
            XMGApp *app = [XMGApp appWithDict:dict];
            [models addObject:app];
        }
        _apps = [models copy];
    }
    return _apps;
}

@end

下载单张图片示例

NSURL *url = [NSURL URLWithString:@"http://example.com/image.jpg"];
[[SDWebImageManager sharedManager] loadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL *targetURL) {
    NSLog(@"正在下载:%zd / %zd", receivedSize, expectedSize);
} completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
    if (image) {
        // 下载成功
        NSLog(@"下载成功:%@", image);
    }
}];

结论

在 iOS 开发中,通过 NSOperationQueue 实现多图片下载,可以了解其基本原理和操作步骤。而使用 SDWebImage 库,可以大大简化图片下载和缓存管理,提升开发效率。

posted @ 2015-08-20 16:51  Mr.陳  阅读(883)  评论(0编辑  收藏  举报