图片下载-->显示:
异步下载: NSOperation + 操作队列
业务逻辑: 图片缓存(防止图片错位,提升效率) + 操作缓存(防止重复创建操作) + 沙盒缓存(磁盘缓存)
业务拆分: 不同的功能写在不同的类中.高层次的封装.
要求:一句话自动实现异步图片的下载并且显示图片. 沙盒缓存(磁盘缓存)
一句话代码的接口: UIImageView 的一个分类.这个分类专门负责提供一句话代码的接口.
异步图片下载: 自定义的操作.专门负责下载图片
显示图片: 管理工具类.负责将图片下载和图片显示联系起来(业务功能逻辑由它实现(协调)). 图片缓存+操作缓存
沙盒缓存: 自定义的沙盒缓存工具类.专门负责沙盒中图片的读和写.
// 解决图片错位问题,需要判定 cell 对应的图片地址已经改变!
给每一个imageView 都绑定一个下载地址. 如果外界传入的下载地址改变, 让 iamgeView 绑定的地址变成新的地址,原来的下载操作取消.开始新的下载操作.
异步下载: NSOperation + 操作队列
业务逻辑: 图片缓存(防止图片错位,提升效率) + 操作缓存(防止重复创建操作) + 沙盒缓存(磁盘缓存)
业务拆分: 不同的功能写在不同的类中.高层次的封装.
要求:一句话自动实现异步图片的下载并且显示图片. 沙盒缓存(磁盘缓存)
一句话代码的接口: UIImageView 的一个分类.这个分类专门负责提供一句话代码的接口.
异步图片下载: 自定义的操作.专门负责下载图片
显示图片: 管理工具类.负责将图片下载和图片显示联系起来(业务功能逻辑由它实现(协调)). 图片缓存+操作缓存
沙盒缓存: 自定义的沙盒缓存工具类.专门负责沙盒中图片的读和写.
// 解决图片错位问题,需要判定 cell 对应的图片地址已经改变!
给每一个imageView 都绑定一个下载地址. 如果外界传入的下载地址改变, 让 iamgeView 绑定的地址变成新的地址,原来的下载操作取消.开始新的下载操作.
//
如何给
imageView
绑定下载地址:利用运行时,在分类中动态的为
imageView
添加一个属性(urlString).
具体的NSOperation(操作)类
具体的工具类(管理类)解决图片缓存(防止图片错位,提升效率) + 操作缓存(防止重复创建操作)
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 1. 定义block类型
@class SHDownloadWebImageOperation;
typedef void(^downloadBlock)(SHDownloadWebImageOperation *op);
@interface SHDownloadWebImageOperation : NSOperation
// 负责下载图片
// 图片下载地址
@property(nonatomic ,copy)NSString *urlString;
// 下载好的图片
@property(nonatomic ,strong)UIImage *image;
// 添加block属性
@property(nonatomic ,copy) downloadBlock block;
// 网络图片下载完成之后的回调.(将图片添加到内存缓存中)
-(void)downloadWebImageWithBlock:(downloadBlock)blk;
@end
#import "SHDownloadWebImageOperation.h"
#import "SHReadAndWriteSandbox.h"
@implementation SHDownloadWebImageOperation
-(void)main
{
@autoreleasepool {
// 下载图片
self.image = [self downloadWebImageWithUrlString:self.urlString];
// 图片下载完成之后,回到主线程执行图片下载完成之后的回调.
dispatch_async(dispatch_get_main_queue(), ^{
if (self.block) {
// 3. 执行 self.block ,需要的参数 self.
self.block(self);
}
});
}
}
// 2. 确定 self.block 中执行的内容.
-(void)downloadWebImageWithBlock:(downloadBlock)blk
{
if (blk) {
self.block = blk;
}
}
// 下载网络图片.
-(UIImage *)downloadWebImageWithUrlString:(NSString *)urlString
{
NSLog(@"下载图片:%@",[NSThread currentThread]);
if (self.isCancelled) {
return nil;
}
// 模拟网络过程...
[NSThread sleepForTimeInterval:3];
if (self.isCancelled) {
return nil;
}
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
// 将图片存入沙盒.
// [[SHReadAndWriteSandbox sharedSandbox] writeDataToSandbox:data urlString:urlString];
}
if (self.isCancelled) {
return nil;
}
UIImage *image = [UIImage imageWithData:data];
return image;
}
@end
具体的工具类(管理类)解决图片缓存(防止图片错位,提升效率) + 操作缓存(防止重复创建操作)
//
// SHWebImageManager.h
// SHWebImage
//
// Created by teacher on 16/1/9.
// Copyright © 2016年 teacher. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
// 1.定义block类型(显示图片)
typedef void(^setUpImageBlock)(UIImage *image);
@interface SHWebImageManager : NSObject
// 图片缓存
@property(nonatomic ,strong) NSCache *images;
// 操作缓存
@property(nonatomic ,strong) NSMutableDictionary *operations;
//管理类.负责图片缓存和操作缓存. 单例.
+(instancetype)sharedManager;
// 下载图片的接口
// urlString: 图片的下载地址
// setUpImageBlk :图片下载完成之后的回调.(显示图片)
-(void)downloadWebImageWithUrlString:(NSString *)urlString setUpImageWithBlock:(setUpImageBlock)setUpImageBlk;
// 取消下载操作
-(void)cancelOperationWithUrlString:(NSString *)urlString;
@end
.m文件
//
// SHWebImageManager.m
// SHWebImage
//
// Created by teacher on 16/1/9.
// Copyright © 2016年 teacher. All rights reserved.
//
#import "SHWebImageManager.h"
#import "SHDownloadWebImageOperation.h"
@interface SHWebImageManager ()
// 操作队列
@property(nonatomic ,strong) NSOperationQueue *queue;
@end
@implementation SHWebImageManager
-(void)cancelOperationWithUrlString:(NSString *)urlString
{
NSLog(@"取消原来的操作");
// 1. 取出 urlString 对应的操作]
SHDownloadWebImageOperation *op = [self.operations objectForKey:urlString];
// 2. 取消操作
[op cancel];
// 3. 将操作从操作缓存中移除(可以重新下载)
[self.operations removeObjectForKey:urlString];
}
// 下载图片
-(void)downloadWebImageWithUrlString:(NSString *)urlString setUpImageWithBlock:(setUpImageBlock)setUpImageBlk
{
// 1. 创建操作
SHDownloadWebImageOperation *downOp = [[SHDownloadWebImageOperation alloc] init];
// 2. 告诉操作下载哪一张图片
downOp.urlString = urlString;
// 3. 图片下载完成之后的回调.
[downOp downloadWebImageWithBlock:^(SHDownloadWebImageOperation *op) {
if (op.image) {
// 显示图片
if (setUpImageBlk) {
NSLog(@"图片下载完毕之后,直接显示图片");
setUpImageBlk(op.image);
}
// 将图片添加到图片缓存中
[self.images setObject:op.image forKey:urlString];
}
// 操作完成之后,清空操作缓存中的对应的操作
[self.operations removeObjectForKey:urlString];
}];
// 4. 将操作添加到操作缓存中
[self.operations setObject:downOp forKey:urlString];
// 5. 将操作添加到操作队列中
[self.queue addOperation:downOp];
}
//懒加载
-(NSMutableDictionary *)operations
{
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
<pre name="code" class="objc" style="color: rgb(0, 132, 0); font-size: 18px;">//懒加载
-(NSCache *)images{ if (!_images) { _images = [[NSCache alloc] init]; } return _images;}-(NSOperationQueue
*)queue{ if (!_queue) { _queue = [[NSOperationQueue alloc] init];
//设置最大并发数
[_queue setMaxConcurrentOperationCount:6];
}
return _queue;
}
//初始化单例对象
+(instancetype)sharedManager
{
static id _instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
@end
沙盒缓存----解决读取数据问题
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SHReadAndWriteSandbox : NSObject
// 工具类: 单例.
// 提供实现单例的方法:
+(instancetype)sharedSandbox;
// 1.获得具体的文件路径(沙盒路径)
-(NSString *)getFilePathWithUrlString:(NSString *)urlString;
// 2.直接从沙盒中获取图片
-(UIImage *)getImageFromSandboxWithUrlString:(NSString *)urlString;
// 3.将数据写入沙盒
-(void)writeDataToSandbox:(NSData *)data urlString:(NSString *)urlString;
@end
.m文件
#import "SHReadAndWriteSandbox.h"
@implementation SHReadAndWriteSandbox
+(instancetype)sharedSandbox
{
static id _instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
// 返回值: 具体的文件路径.
-(NSString *)getFilePathWithUrlString:(NSString *)urlString
{
NSString *filePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%@",filePath);
});
filePath = [filePath stringByAppendingPathComponent:urlString.lastPathComponent];
return filePath;
}
-(UIImage *)getImageFromSandboxWithUrlString:(NSString *)urlString
{
// 1. 文件路径
NSString *filePath = [self getFilePathWithUrlString:urlString];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
return image;
}
-(void)writeDataToSandbox:(NSData *)data urlString:(NSString *)urlString
{
[data writeToFile:[self getFilePathWithUrlString:urlString] atomically:YES];
}
@end
UIImageView 的一个分类.这个分类专门负责提供一句话代码的接口
//
// UIImageView+WebImage.h
// SHWebImage
//
// Created by teacher on 16/1/9.
// Copyright © 2016年 teacher. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@interface UIImageView (WebImage)
// @property :自动添加 set 和 get 方法.
// 分类中需要手动实现,利用运行时实现.动态的添加.
@property(nonatomic ,copy) NSString *urlString;
// 一句话代码下载图片的接口:
// urlString : 图片的下载地址.
// placeholderImage :占位图片.
-(void)downloadWebImageWithUrlString:(NSString *)urlString placeholderImage:(UIImage *)placeholderImage;
@end
.m文件
//
// UIImageView+WebImage.m
// SHWebImage
//
// Created by teacher on 16/1/9.
// Copyright © 2016年 teacher. All rights reserved.
//
#import "UIImageView+WebImage.h"
#import "SHWebImageManager.h"
#import "SHReadAndWriteSandbox.h"
#import "SHDownloadWebImageOperation.h"
@implementation UIImageView (WebImage)
const void * urlStringKey = "urlStringKey";
-(void)setUrlString:(NSString *)urlString
{
// 参数:
// 1. 为谁增加属性? self.
// 2. 取值的 key .
// 3. 需要设置的值 value.
// 4. 安全策略(定时属性的时候选择的策略(原子/非原子,copy/retain/strong))
objc_setAssociatedObject(self, urlStringKey, urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)urlString
{
return objc_getAssociatedObject(self, urlStringKey);
}
-(void)downloadWebImageWithUrlString:(NSString *)urlString placeholderImage:(UIImage *)placeholderImage
{
// 下载并且显示图片.
// self.image = image;
// 1. 实例化图片下载管理类
SHWebImageManager *manager = [SHWebImageManager sharedManager];
// 什么时候才需要下载图片?
// 1. 检查内存缓存中是否存在图片.
UIImage *image = [manager.images objectForKey:urlString];
if (image) {
NSLog(@"直接从内存缓存中取出图片");
self.image = image;
return;
}
// 2. 内存缓存中没有图片,检查沙盒缓存中的图片
UIImage *sandboxImage = [[SHReadAndWriteSandbox sharedSandbox] getImageFromSandboxWithUrlString:urlString];
if (sandboxImage) {
NSLog(@"从沙盒中读取图片");
// 1.显示图片
self.image = sandboxImage;
// 2.将沙盒缓存中的图片添加到内存缓存中
// 目的: 减少 I/O 操作.
[manager.images setObject:sandboxImage forKey:urlString];
return;
}
// 3. 添加占位图片
if (placeholderImage) {
self.image = placeholderImage;
}
// 判断这个操作是否已经存在?(判断跟imageView 绑定的图片下载地址是否改变!)
if (self.urlString && [self.urlString isEqualToString:urlString]) {
// self.urlString 存在并且传入的新地址跟原来的一样! 等待下载完毕.
NSLog(@"操作已经存在,等待下载完毕...");
return;
}
if (self.urlString && ![self.urlString isEqualToString:urlString]) {
// self.urlString 存在,传入的新地址和原来的不同
NSLog(@"取消原来的操作,开始下载新的图片");
// 取消原来的下载操作
[manager cancelOperationWithUrlString:self.urlString];
// 给 self.urlString 绑定新的值.
self.urlString = urlString;
//开始新的下载操作 // 重新下载
[self downloadWebImageWithUrlString:self.urlString];
return;
}
if (!self.urlString) { // 第一次下载.
NSLog(@"第一次下载图片");
// imageView 绑定图片下载地址
self.urlString = urlString;
[self downloadWebImageWithUrlString:self.urlString];
}
}
// 重新开始下载图片
- (void)downloadWebImageWithUrlString:(NSString *)urlString
{
SHWebImageManager *manager = [SHWebImageManager sharedManager];
// 4. 检查操作缓存中是否存在这个操作.
SHDownloadWebImageOperation *op = [manager.operations objectForKey:urlString];
if (op) {
// 操作存在,等待下载结束.
return;
}
// 5. 下载图片
[manager downloadWebImageWithUrlString:urlString setUpImageWithBlock:^(UIImage *image) {
// 显示图片.
self.image = image;
}];
}
@end