1、SDWebImage中用到的运行时机制。
导入头文件
#import
@implementation UIImageView (WebCache)
const char *previousURLStringKey = "previousURLStringKey";
- (NSString *)previousURLString
{
return objc_getAssociatedObject(self, previousURLStringKey);
}
// 这里要用到的是运行时机制,分类不能增加属性,只能增加方法
// 这个方法是动态的为某一个类添加属性
// 指的将要绑定的对象
// 键名
// 保存的是哪个值
//
objc_setAssociatedObject(self, previousURLStringKey, previousURLString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (void)setImageWithURLString:(NSString *)URLString
{
DHOperationManager *manager = [DHOperationManager sharedManager];
if ([URLString isEqualToString:self.previousURLString] == NO) {
// 当前的URLString不等于previousURLString
// 取消上一个的下载任务
[manager cancelOperationWith:self.previousURLString];
}
// 根据当前的URLString去下载
[manager downloadWithURLString:URLString andFinishedBlock:^(UIImage *image) {
self.image = image;
NSLog(@"%@----",[NSThread currentThread]);
}];
self.previousURLString = URLString;
}
2、自定义操作
// 1/先调start,后调main
- (void)start
{
NSLog(@"%s",__func__);
[super start];
}
- (void)main
{
NSLog(@"%s",__func__);
@autoreleasepool {
// 在这里不断监测
if (self.isCancelled) {
return;
}
// 这个方法默认已经帮我们创建好了子线程,不需要再开启新的线程
NSURL *url = [NSURL URLWithString:self.innerURLString];
// 在这里不断监测
if (self.isCancelled) {
return;
}
[NSThread sleepForTimeInterval:3.0];
NSData *data = [NSData dataWithContentsOfURL:url];
// 在这里不断监测
if (self.isCancelled) {
return;
}
if (data) {
// 将二进制数据存储到沙盒中
[data writeToFile:[NSString fileNameWithURLString:self.innerURLString] atomically:YES];
}
UIImage *image = [UIImage imageWithData:data];
// 在这里不断监测
if (self.isCancelled) {
return;
}
// 调用block传递参数,这个block已经放在主线程上执行,
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 在这里不断监测
if (self.isCancelled) {
return;
}
self.innerFinishedBlock(image);
}];
//NSLog(@"%@",[NSThread currentThread]);
}
}
3、管理者
// 操作缓存
- (NSMutableDictionary *)operationCache
{
if (_operationCache == nil) {
_operationCache = [NSMutableDictionary dictionary];
}
return _operationCache;
}
// 内存缓存
- (NSCache *)imageCache
{
if (_imageCache == nil) {
_imageCache = [[NSCache alloc] init];
}
return _imageCache;
}
// 并发队列的懒加载
- (NSOperationQueue *)concurrentQueue
{
if (_concurrentQueue == nil) {
_concurrentQueue = [[NSOperationQueue alloc] init];
}
return _concurrentQueue;
}
static DHOperationManager *_instance;
+ (instancetype)sharedManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (instancetype)init
{
if (self = [super init]) {
// 添加观察者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanImageCache) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
// 移除监听
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)downloadWithURLString:(NSString *)URLString andFinishedBlock:(FinishedBlock)finishedBlock
{
// 1、先从内存中取
UIImage *ramImage = [self.imageCache objectForKey:URLString];
if (ramImage) {
NSLog(@"ram");
finishedBlock(ramImage);
return;
}
// 2、从沙盒中取
NSData *data = [NSData dataWithContentsOfFile:[NSString fileNameWithURLString:URLString]];
if (data) {
NSLog(@"local");
// 将二进制数据转化成image
UIImage *localImage = [UIImage imageWithData:data];
// 将取出来的存放到内存zhong
[self.imageCache setObject:localImage forKey:URLString];
finishedBlock(localImage);
return;
}
// 3、判断该操作是否已经存在,防止用户反复点击
if ([self.operationCache objectForKey:URLString]) {
NSLog(@"loading");
return;
}
// 4、去下载
DHDownloadImageOperation *operation = [DHDownloadImageOperation downloadImageOperationWithURLString:URLString andFinishedBlock:^(UIImage *image) {
// 1、将图片缓存写入内存中
[self.imageCache setObject:image forKey:URLString];
// 将下载好的操作移出字典中
[self.operationCache removeObjectForKey:URLString];
NSLog(@"download");
// 2、调用传入的block
finishedBlock(image);
}];
// 5、将操作放入到队列中
[self.concurrentQueue addOperation:operation];
// 将操作放入字典中
[self.operationCache setObject:operation forKey:URLString];
}
- (void)cancelOperationWith:(NSString *)URLString
{
// 根据URLString在缓存中取出操作
DHDownloadImageOperation *operation = [self.operationCache objectForKey:URLString];
// 当URLString改变过快,此时可能操作字典中还没有将任务添加到字典
// 所以先判断当前的操作在不在字典中
if (operation == nil) {
return;
}
//操作字典中存放的是正在执行的操作
// 取消当前操作
// cancel只是给当前任务一个标签并不会立即停止该任务,要在任务执行的时候根据这个信号去判断是继续执行还是立即返回
[operation cancel];
// 从当前操作字典中移除
[self.operationCache removeObjectForKey:URLString];
}
- (void)cleanImageCache
{
// 清除缓存
[self.imageCache removeAllObjects];
}