多线程(四)之cell图片下载(SDWebImage的认识总结)

时间:2022-06-08 21:10:34

1.SDWebImage

首先这是个很有名的第三方框架,功能主要是:图片下载,图片缓存,下载进度监听和gif处理等..

本次的目的是了解一下底层实现和框架的使用.

1.仿写SDWebImage

1.1自定义下载图片的NSOperation

实现起来很简单:思路如下:

1.我要下载图片在模拟器或者真机显示,肯定第一步就是在sb中拉一个imageview控件.

2.肯定要在控制器中设置一个全局属性的queue队列,一般是要懒加载一下,目的也就是实例化队列queue.

3.然后就是采用多线程中的NSOperation来下载图片,NSOperation是一个抽象类,没有直接的实例化方法,所以用的时候是用他的两个子类去创建操作对象,或者自定义一个操作类,一般采用自定义操作类,为什么呢?因为我们一般下载操作会封装起来,提供接口供控制器使用.

4.注意,在自定义的操作类中,思考一个问题,在哪个方法中去执行下载任务,答案是main方法,而这个方法比较有意思,它默认由NSOperation中的start方法调用,所以也就是我调用start方法后默认会执行main方法,还有一个就是,当我不去调用start方法,而是把任务添加到队列中,这个时候默认会调用start方法.

一个常识哦,苹果的plus版,frame宽:414高:736 ,如果面试问道,可以说自己使用的时候都是用自动布局,设置四周边距为0 ,不一定非要记得很清楚

 

分别用代理通知和block完成对图片的异步下载操作,上传到文件中了

2.自定义图片下载操作

代理:

//
// HMDownloadIMGOperation.h
// 自定义Operation
//
// Created by apple on 16/8/23.
// Copyright © 2016年 itcast. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

//自定义一个专门用于下载图片的操作
//1.继承
//2.查看父类的头文件,查找合适的方法来重写
//3.需要把子线程下载完的数据还给调用方
//待解决的疑问: 如何给数据 线程


//代理
@protocol HMDownloadOpDelegate;

@interface HMDownloadIMGOperation : NSOperation

@property(nonatomic,weak)
id<HMDownloadOpDelegate> delegate;
//从外部指定的图片下载地址
@property(nonatomic,copy) NSString *imageURL;

@end

@protocol HMDownloadOpDelegate <NSObject>

@optional
-(void)downloadFinish:(UIImage *)image;

@end
//
// HMDownloadIMGOperation.m
// 自定义Operation
//
// Created by apple on 16/8/23.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "HMDownloadIMGOperation.h"

@implementation HMDownloadIMGOperation

/*
main
The default implementation of this method does nothing. You should override this method to perform the desired task. In your implementation, do not invoke super. This method will automatically execute within an autorelease pool provided by NSOperation, so you do not need to create your own autorelease pool block in your implementation.
该方法默认没有做任何事情,你应该在此方法内执行自己的任务.在实现中,不要调用父类的方法,该方法已经创建了自动释放池,你就没有必要再去创建.---->图片下载的代码写在main中
*/


/*
This method also performs several checks to ensure that the operation can actually run.
该方法执行好几个检查保证操作能正确运行
*/
-(void)start{
// NSLog(@"start %@",[NSThread currentThread]);
[super start];
}

-(void)main{
//不要子弟手动去调用,操作在运行的时候回自己调用
//下载图片的代码
NSURL *url = [NSURL URLWithString:self.imageURL];
NSData
*data = [NSData dataWithContentsOfURL:url];
UIImage
*image = [UIImage imageWithData:data];
// NSLog(@"%@",[NSThread currentThread]);

if([self.delegate respondsToSelector:@selector(downloadFinish:)]){
//由于回到VC之后,=一般极有可能是进行UI操作,那么可以在mian方法中先做线程通信

[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
[self.
delegate downloadFinish:image];
}];
}

}
@end
//
// ViewController.m
// 自定义Operation
//
// Created by apple on 16/8/23.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "ViewController.h"
#import "HMDownloadIMGOperation.h"

@interface ViewController ()<HMDownloadOpDelegate>
@property (weak, nonatomic) IBOutlet UIImageView
*imageview;

@property(nonatomic,strong) NSOperationQueue
*queue;

@end

@implementation ViewController

-(NSOperationQueue *)queue{
if(!_queue){
_queue
= [[NSOperationQueue alloc] init];
}
return _queue;
}

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self demo1];

}

- (void)demo1 {
NSString
*imgUrl = @"http://h.hiphotos.baidu.com/image/pic/item/b219ebc4b74543a9bd0374211a178a82b80114c6.jpg";
HMDownloadIMGOperation
*downloadIMGOp = [[HMDownloadIMGOperation alloc] init];

downloadIMGOp.imageURL
= imgUrl;
downloadIMGOp.
delegate = self;
//在当前线程
// [downloadIMGOp start];
[self.queue addOperation:downloadIMGOp];


}

#pragma mark delegate
-(void)downloadFinish:(UIImage *)image{
NSLog(
@"%@",[NSThread currentThread]);
self.imageview.image
= image;
}

@end

通知:

//
// HMDownloadIMGOperation.m
// 自定义Operation
//
// Created by apple on 16/8/23.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "HMDownloadIMGOperation.h"

@implementation HMDownloadIMGOperation

/*
main
The default implementation of this method does nothing. You should override this method to perform the desired task. In your implementation, do not invoke super. This method will automatically execute within an autorelease pool provided by NSOperation, so you do not need to create your own autorelease pool block in your implementation.
该方法默认没有做任何事情,你应该在此方法内执行自己的任务.在实现中,不要调用父类的方法,该方法已经创建了自动释放池,你就没有必要再去创建.---->图片下载的代码写在main中
*/


/*
This method also performs several checks to ensure that the operation can actually run.
该方法执行好几个检查保证操作能正确运行
*/
-(void)start{
// NSLog(@"start %@",[NSThread currentThread]);
[super start];
}

-(void)main{
//不要子弟手动去调用,操作在运行的时候回自己调用
//下载图片的代码
NSURL *url = [NSURL URLWithString:self.imageURL];
NSData
*data = [NSData dataWithContentsOfURL:url];
UIImage
*image = [UIImage imageWithData:data];
// NSLog(@"%@",[NSThread currentThread]);


[[NSOperationQueue mainQueue] addOperationWithBlock:
^{
//通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"girlimage" object:image];
}];

}
@end
//
// ViewController.m
// 自定义Operation
//
// Created by apple on 16/8/23.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "ViewController.h"
#import "HMDownloadIMGOperation.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView
*imageview;

@property(nonatomic,strong) NSOperationQueue
*queue;

@end

@implementation ViewController

-(NSOperationQueue *)queue{
if(!_queue){
_queue
= [[NSOperationQueue alloc] init];
}
return _queue;
}

- (void)viewDidLoad {
[super viewDidLoad];

[self demo1];

//监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(uiupdate:) name:@"girlimage" object:nil];

}

//通知接受的方法
-(void)uiupdate:(NSNotification *)notification{
NSLog(
@"11 %@",[NSThread currentThread]);
UIImage
*image = notification.object;
self.imageview.image
= image;
}

-(void)dealloc{
//销毁通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)demo1 {
HMDownloadIMGOperation
*downloadIMGOp = [[HMDownloadIMGOperation alloc] init];

downloadIMGOp.imageURL
= @"http://b.hiphotos.baidu.com/image/pic/item/77094b36acaf2edd9a034601891001e938019363.jpg";
//在当前线程
[downloadIMGOp start];
// [self.queue addOperation:downloadIMGOp];
}

@end

block:

//
// HMDownloadIMGOperation.h
// 自定义图片下载操作01
//
// Created by 曹魏 on 16/8/28.
// Copyright © 2016年 itcast. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/*
接下来就要用到代理了,代理在这里也就是传个值而已

*/
//声明一个协议
//@protocol HMDownloadIMGOperationDelegate <NSObject>
//
//- (void)downloadFinish:(UIImage *)image;
//
//@end

@interface HMDownloadIMGOperation : NSOperation
@property (nonatomic,copy) NSString
*imgURL;

//@property (nonatomic,weak) id <HMDownloadIMGOperationDelegate> delegate;
//block回调
@property (nonatomic,copy) void(^finishBlock)(UIImage *image);

@end
//
// HMDownloadIMGOperation.m
// 自定义图片下载操作01
//
// Created by 曹魏 on 16/8/28.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "HMDownloadIMGOperation.h"
//1.自定义一个继承自NSOperation的类,用来专门实现图片下载的操作
/*

思路:定义一个图片地址接口,为什么?
既然是专门下载图片的类,就需要满足低耦合的特性,不能是死的
当然可以不用代理,直接在这个类中引入控制器的类,把参数传递过来,但这么
做,这个类就会依赖控制器,就不能方便的作为一个封装好的类供别的程序使用
严重违反了低耦合的特性,一看就是培训出来的大水比,所以切记代码规范
*/

@implementation HMDownloadIMGOperation
- (void)start{
[super start];
}
//main方法就是用来执行下载操作
//那么重点来了,怎么下载?
- (void)main{
//套路走起来
NSURL *url = [NSURL URLWithString:self.imgURL];
NSData
*data = [NSData dataWithContentsOfURL:url];
UIImage
*image = [UIImage imageWithData:data];
// if ([self.delegate respondsToSelector:@selector(downloadFinish:)]) {
// [[NSOperationQueue mainQueue] addOperationWithBlock:^{
//
// [self.delegate downloadFinish:image];
//
// }];
// }

[[NSOperationQueue mainQueue]addOperationWithBlock:
^{
if (self.finishBlock) {
self.finishBlock(image);
}

}];


}

@end
//
// ViewController.m
// 自定义图片下载操作01
//
// Created by 曹魏 on 16/8/28.
// Copyright © 2016年 itcast. All rights reserved.
//
/*

假设:我是客户,我给你提出一个需求,让你用代理写一个异步下载图片的程序,怎么做?
首先用代理,想到必须是另写一个用于下载图片的类,而且,控制器那边必须负责提供图片地址,
其次:异步下载图片,必须有队列


*/
#import "ViewController.h"
#import "HMDownloadIMGOperation.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView
*iconImage;
//队列的套路
@property (nonatomic,strong) NSOperationQueue *queue;
@end



@implementation ViewController
//队列懒加载(确切说是初始化),懒加载是个方法,必须写在方法的实现里面,但要写在加载之前
- (NSOperationQueue *)queue{
if (_queue == nil) {

_queue
= [[NSOperationQueue alloc]init];

}

return _queue;
}
/*
ViewController是程序的入口,目前看来其作用就是仅仅从此入口进入HMDownloadIMGOperation.h的入口

*/
- (void)viewDidLoad {
[super viewDidLoad];
[self demo1];

}
- (void)demo1{
NSString
*imgUrl = @"http://h.hiphotos.baidu.com/image/pic/item/b219ebc4b74543a9bd0374211a178a82b80114c6.jpg";

HMDownloadIMGOperation
*operation = [[HMDownloadIMGOperation alloc]init];
//start 属于nsoperation的方法,该方法是个桥梁,会调用main方法,如果有队列,会默认执行start方法
// operation.delegate = self;
operation.imgURL = imgUrl;
#pragma mark -- 这一步是干嘛的?
/*
这一步说白了就是对finishBlock这个属性赋值,但要知道
这是个block,所以要想实现这一步操作,必须要有人去调用
也就是得有个finishBlock();的方法,才会执行这一步.

*/
[operation setFinishBlock:
^(UIImage *image) {
self.iconImage.image
= image;

}];



// [operation start];所以此行注释掉
[self.queue addOperation:operation];




}


////实现代理方法
//- (void)downloadFinish:(UIImage *)image{
//
// self.iconImage.image = image;
//
//
//}
@end

 3.模拟SD框架:

 目的是通过一个存有图片下载地址的plist文件中,点击屏幕随机下载一张图片

步骤:

1:懒加载,字典转模型,定义一个随机数,获取随机选出的模型对象

 

//点击屏幕随机下载一张图片
/*
arc4random_uniform会随机返回一个0到上界之间(不含上界)的整数。以2为上界会得到0或1,像投硬币一样
*/
uint32_t number
= arc4random_uniform((uint32_t)self.dataArray.count);
//随机取出一个模型对象
CWDownloadIMGModel *model = self.dataArray[number];

2.自定义操作对象,用于下载图片,异步并发下载,注意此时有用到block用来传递下载好的图片

- (void)main{
NSURL
*url = [NSURL URLWithString:self.imageURL];
NSData
*data = [NSData dataWithContentsOfURL:url];
UIImage
*image = [UIImage imageWithData:data];
[[NSOperationQueue mainQueue] addOperationWithBlock:
^{

if (self.finishBlock) {
self.finishBlock(image);
}

}];

}

3.图片下载完成,模拟网络延迟,会出现图片跳的现象,此时解决方法:判断两次点击后的图片的下载地址是否一致,如果不一致,说明一定进行了多次点击,取消上一次的点击的操作对象,以最后一次点击为准

#pragma mark -- 异步下载图片
CWDownloadOperation
*operation = [CWDownloadOperation downloadOperationWithUrl:imgUrl andFinishBlock:^(UIImage *image) {
// self.iconImage.image = image;
finishBlock(image);

//图片下载完成后记得把操作缓存删除
[self.cacheOperation removeObjectForKey:imgUrl];
//把图片添加到内存缓存中
[self.cacheImg setObject:image forKey:imgUrl];
}];
//把操作添加到队列中,别忘了ATS
[self.queue addOperation:operation];
//把操作添加到操作缓存中
[self.cacheOperation setObject:operation forKey:imgUrl];

}
//取消下载
- (void)cancelDownloadWithUrl:(NSString *)imgUrl{
//取出上一次点击的操作
CWDownloadOperation *lastOperation = self.cacheOperation[imgUrl];
//取消操作,cancel方法是不能够让操作停止的,只是能改变操作的状态,还需要手动取消
[lastOperation cancel];
}

4.管理类,定义一个专门用于下载和取消下载的管理类,其实就是为了给控制器瘦身,封装的一个单例类

//定义一个单例类
+ (instancetype)sharedDownloadIMGManager{
static id instance;
static dispatch_once_t onceToken;
dispatch_once(
&onceToken, ^{
instance
= [[self alloc]init];

});
return instance;
}

5.缓存:内存缓存和沙盒缓存,内存缓存很简单,其实就是定义一个可变字典,把图片放进去拦截下载操作,沙盒缓存:把图片放进沙盒必须要找到data,然后writeTofile,拼接沙河路径

//下载图片
#warning mark -- 注意在写接口方法的时候,如果有block参数,里面并不是给block赋值,只有当调用接口方法的时候,才会传值,此时如果有block参数,就是给block参数赋值

- (void)downloadIMGManagerWithURL:(NSString *)imgUrl andFinishBlock:(void (^)(UIImage *))finishBlock{
//判断
UIImage *image = self.cacheImg[imgUrl];
if (image) {
NSLog(
@"内存缓存 :%@",imgUrl);
finishBlock(image);
return;
}
else{
//从沙盒中取出图片
UIImage *sandBoxImage = [UIImage imageWithContentsOfFile:[imgUrl appendCachePath]];
if (sandBoxImage) {
NSLog(
@"沙盒缓存 :%@",imgUrl);
finishBlock(sandBoxImage);
//性能优化
[self.cacheImg setObject:sandBoxImage forKey:imgUrl];
return;

}
}
//
// NSString+Path.h
// 02-沙盒目录
//
// Created by apple on 15/6/2.
// Copyright (c) 2015年 heima. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSString (Path)

/// 追加文档目录
- (NSString *)appendDocumentPath;
/// 追加缓存目录
- (NSString *)appendCachePath;
/// 追加临时目录
- (NSString *)appendTempPath;

@end
//
// NSString+Path.m
// 02-沙盒目录
//
// Created by apple on 15/6/2.
// Copyright (c) 2015年 heima. All rights reserved.
//

#import "NSString+Path.h"

@implementation NSString (Path)

- (NSString *)appendDocumentPath {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:self.lastPathComponent];
}

- (NSString *)appendCachePath {
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:self.lastPathComponent];
}

- (NSString *)appendTempPath {
return [NSTemporaryDirectory() stringByAppendingPathComponent:self.lastPathComponent];
}

@end

6.继续给控制器瘦身,用一句代码完成下载,此时控制器中只有了给imageview赋值的操作

法一:自定义一个继承自UIimageview的子类,别忘了修改storyBoard中的控件名称还有控制器中的类名

//
// CWDownloadImageView.h
// 模拟SD图片下载第三次小练习
//
// Created by 曹魏 on 2016/12/26.
// Copyright © 2016年 itcast. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface CWDownloadImageView : UIImageView
//定义一个接收图片地址的变量
@property (nonatomic,copy) NSString *currentImg;

//下载和取消下载
- (void)downloadWithString:(NSString *)urlString;
@end
//
// CWDownloadImageView.m
// 模拟SD图片下载第三次小练习
//
// Created by 曹魏 on 2016/12/26.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "CWDownloadImageView.h"
#import "CWDownloadIMGManager.h"
@implementation CWDownloadImageView

- (void)downloadWithString:(NSString *)urlString{
//当新点击的图片的下载地址和上一次不同的时候一定能判断出是不同的两张图片
if (![self.currentImg isEqualToString:urlString]) {
[[CWDownloadIMGManager sharedDownloadIMGManager] cancelDownloadWithUrl:self.currentImg];
}

//这一步是先于前一步的
self.currentImg = urlString;
[[CWDownloadIMGManager sharedDownloadIMGManager] downloadIMGManagerWithURL:urlString andFinishBlock:
^(UIImage *image) {
self.image
= image;

}];

}

@end

法二:添加分类,给UIimageview添加分类

//
// UIImageView+CWDownloadImg.h
// 模拟SD图片下载第三次小练习
//
// Created by 曹魏 on 2016/12/26.
// Copyright © 2016年 itcast. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIImageView (CWDownloadImg)
//定义一个接收图片地址的变量
@property (nonatomic,copy) NSString *currentImg;

//下载和取消下载
- (void)downloadWithString:(NSString *)urlString;
@end
//
// UIImageView+CWDownloadImg.m
// 模拟SD图片下载第三次小练习
//
// Created by 曹魏 on 2016/12/26.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "UIImageView+CWDownloadImg.h"
#import "CWDownloadIMGManager.h"
#import <objc/runtime.h>
const char *KEY = "key";
@implementation UIImageView (CWDownloadImg)
- (void)setCurrentImg:(NSString *)currentImg{
objc_setAssociatedObject(self, KEY, currentImg, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)currentImg{
return objc_getAssociatedObject(self, KEY);
}
- (void)downloadWithString:(NSString *)urlString{
//当新点击的图片的下载地址和上一次不同的时候一定能判断出是不同的两张图片
if (![self.currentImg isEqualToString:urlString]) {
[[CWDownloadIMGManager sharedDownloadIMGManager] cancelDownloadWithUrl:self.currentImg];
}

//这一步是先于前一步的
self.currentImg = urlString;
[[CWDownloadIMGManager sharedDownloadIMGManager] downloadIMGManagerWithURL:urlString andFinishBlock:
^(UIImage *image) {
self.image
= image;

}];
}
@end

注意:分类的目的是给原有类添加一些方法,如果在分类中声明一些属性,是不能生成getter和setter方法的,进而更不能生成成员变量,所以,这个时候,必须要补充set和get方法的实现,并且用到了运行时的一些基本知识:关联对象

具体代码见文件

4.SDWebImage的实际用法 

了解的知识:SDWebImage属于第三方框架,也可以说是一种开源协议,

但并不是所有的开源协议都可以拿来随便用的,有些开源协议虽然不收费,但是作者会在他们源代码的license(许可证)中说明,你用我的框架必须要公开你的代码,这类开源协议一般是:GPL,LGPL

一般情况下我们用的BSD,Apache,MIT(麻省理工)这些开源协议是没关系的,可以自用也可以商用,但要注意上面两种属于大部分公司明令禁止的

5.位移枚举(知道怎么用,能看懂即可)

 

//
// ViewController.m
// 位移枚举
//
// Created by apple on 16/8/23.
// Copyright © 2016年 itcast. All rights reserved.
//

#import "ViewController.h"

//位移枚举:一个参数可以通过按位或 和 按位与 来匹配多个条件
typedef enum {

ActionEnumTop
= 1 << 0,
//0001->0001 = 1
ActionEnumLeft = 1 << 1,
//0001->0010 = 2
ActionEnumRight = 1 << 2,
//4
ActionEnumBottom = 1 << 3
//8

}ActionEnum;

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self passEnum:ActionEnumTop
| ActionEnumLeft];
//按位或 有1为1 其他为0
//ActionEnumTop 0 0 0 1
//ActionEnumLeft 0 0 1 0
//按位或 0 0 1 1
}

- (void)passEnum:(ActionEnum)actionEnum {

NSLog(
@"%d",actionEnum);

if((actionEnum & ActionEnumTop) == ActionEnumTop){
NSLog(
@"ActionEnumTop");
}
//都为1 为1 其他为0
//actionEnum 0 0 1 1
//ActionEnumTop 0 0 0 1
//按位与 0 0 0 1 = 1 ActionEnumTop

if((actionEnum & ActionEnumLeft) == ActionEnumLeft){
NSLog(
@"ActionEnumLeft");
}

if((actionEnum &ActionEnumRight) == ActionEnumRight){
NSLog(
@"ActionEnumRight");
}

if((actionEnum & ActionEnumBottom)== ActionEnumBottom){
NSLog(
@"ActionEnumBottom");
}
//actionEnum 0 0 1 1
//ActionEnumBottom 1 0 0 0
//按位与 0 0 0 0 =0

}

@end