NSOperation下载图片
首先就是根据图片的url去images中取图片,如果存在直接就将图片显示到cell上,问题是不存在该怎么处理?现在images里面没有图片,里面给界面显示一个占位的图片,然后根据这张图片的url也就是图片的唯一标识符去查看操作队列中是否存在下载标识符为url的图片的操作,如果操作队列里面已经有了下载该张图片的操作,那么等待操作队列开启这个操作的start方法即可,可是如果操作队列中没有下载该张图片的操作,那么就需要先创建一个下载该张图片的操作,最好给这个操作也添加一个唯一标志符,避免重复添加下载该图片的操作对象到同一个操作队列之中。现在已经将下载该图片的操作对象添加到操作队列之中了,只等前一个操作对象执行完毕就会取出该操作对象并调用start方法,当然啦,前提是这个操作队列设置的最大允许并发数是1。当这个操作对象调用start方法执行完下载图片的任务之后取出下载成功的图片展示到界面上,剩下的最后一步尤其重要,就是将已经执行完毕的操作对象从操作队列中移除,可是我就疑惑了,为什么操作队列不能再操作对象执行完毕之后自动移除这个操作对象呢,简直就是不科学呀!居然要我们手动去操作队列里面移除这个已经执行完毕的操作对象,难道只是因为操作队列无法监听和管理操作对象里面封装的任务的执行状态。这也不科学呀,操作对列这么NB,这点小事不可能做不到呀!当然啦,我们经常用到的图片下载都是用沙盒来缓存我们所下载的图片的,知道图片的url后也首先是看看沙盒里面有没有,自然,在图片下载成功的时候,也需要特别注意先要把下载成功的图片存储到沙盒里。
// 自己下载图片
-(void)downloadImage{
// 先从内存缓存中取出图片
UIImage *image = self.images[app.icon];
if (image) { // 内存中有图片
cell.imageView.image = image;
} else { // 内存中没有图片
// 获得Library/Caches文件夹
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获得文件名
NSString *filename = [app.icon lastPathComponent];
// 计算出文件的全路径
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
// 加载沙盒的文件数据
NSData *data = [NSData dataWithContentsOfFile:file];
if (data) { // 直接利用沙盒中图片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 存到字典中
self.images[app.icon] = image;
} else { // 下载图片
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
NSOperation *operation = self.operations[app.icon];
if (operation == nil) { // 这张图片暂时没有下载任务
operation = [NSBlockOperation blockOperationWithBlock:^{
// 下载图片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
// 数据加载失败
if (data == nil) {
// 移除操作
[self.operations removeObjectForKey:app.icon];
return;
}
UIImage *image = [UIImage imageWithData:data];
// 存到字典中
self.images[app.icon] = image;
// 回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
// 将图片文件数据写入沙盒中
[data writeToFile:file atomically:YES];
// 移除操作
[self.operations removeObjectForKey:app.icon];
}];
// 添加到队列中
[self.queue addOperation:operation];
// 存放到字典中
self.operations[app.icon] = operation;
}
}
}
// 对比一下三方库
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"placeholder"] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"下载进度(已经接收的图片字节数/图片的总字节数):%f", 1.0 * receivedSize / expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"下载完图片");
}];
NSOperationQueue
添加一段待执行的代码到NSOperationQueue操作队列之中,有两种办法,其一就是通过一个NSOperation子类的操作对象将待执行的代码封装起来,然后通过NSOperationQueue操作队列的对象调用addOperation这个方法将已经封装代码的操作对象添加到队列之中,这时候NSOperationQueue操作队列里面的操作对象就会自动调用start方法开始执行封装的执行代码,还有就是干脆不实例化操作对象了了,直接通过NSOperationQueue操作队列调用addOperationWithBlock方法将想要执行的代码写到Block之中,这虽然很省力,但是不免要问,这有什么意义呢,我干嘛要在.m文件的方法实现里面实例化一个NSOperationQueue操作队列然后在执行写在这个对象的Block参数的代码块里面的方法呢,直接把待执行的代码写到.m文件多好,根本看不到用NSOperationQueue操作队列对象的意义呀!哈哈,刚才整理完了demo,发现通过NSOperationQueue操作队列对象的addOperationWithBlock方法来来添加任务,简直特别👍,因为系统直接就默认这段代码在子线程中执行了,还有比这更幸福的事情么,我反正想不到开辟一个分线程执行任务更简单的方法了,暂时真的想不到比这还简单的方法呀,当然了,我还有一个巨大的收获,我曾经以为图片下载有多么多么高大上呢,没想到本质上就是把那句卡主线的图片下载代码当成一个任务放到子线程去执行呀,简直不要太简单,好不好?还有一个一意外的收获就是弄懂了沙盒硬盘缓存和在应用中声明字典做内存缓存的区别,而且更让人惊喜的是,无论是硬盘沙盒的读取操作还是在程序运行时开辟一个字典内存空间存储图片都是可以在子线程进行的,简直不要太炫酷好不好?
操作队列设置最大并发数
NSOperationQueue的对象调用setMaxConcurrentOperationCount方法,来设置操作队列最多允许同时执行多少个操作对象,一个操作对象开始执行就表示这个操作对列为这个操作对象开辟了一条新线程,然后这个操作对象就开始调用start方法,将封装在里面的代码放在为此操作对象开辟的线程中执行。
操作队列设置状态
调用cancelAllOperations方法取消操作队列里面的所有操作,调用setSuspended方法暂停和恢复操作队列里面所有的操作对象。也可以针对操作队列里面的某一个操作对象,然后调用cancel方法取消特定操作对象里面封装的代码。
操作队列管理操作与操作之间的依赖
一说到操作与操作之间的依赖,这个用处就很广泛了,典型的就是我的直播间的逻辑,要执行很多操作,这些待执行的操作是有严格的顺序要求的,其实我更多的就是通过Block来实现呀,也可以考虑换一个思路嘛,比如通过操作队列来设置操作与操作之间的依赖关系。 NSOperation之间可以设置依赖来保证执行顺序比如一定要让操作A执行完后,才能执行操作B,可以这么写[operationB addDependency:operationA];表示操作B必须在操作A完成结束之后再执行。最激动人心的是,就算A操作对象在队列1,B操作对象在队列2,也是可以实现操作A在操作B执行完毕之后再执行。就是这么任性。但是切记切记,不要相互依赖呀,你不要整个A依赖B,又整个B依赖A,你这么做可就让程序为难了。
SDWebImage工作原理
最占内存资源的还是图片缓存,问题的关键就在我们如何使用SDWebImage库来缓存图片。方法就是在调用sd_setImageWithURL之后再加上一个有options选项的方法,SDWebImage默认的其它方法都是默认综合存储,也就是内存缓存和磁盘缓存相结合的方式,如果只需要内存缓存,那么在options选项后选择SDWebImageCacheMemoryOnly就行了。
当调用sd_setImageWithURL方法之后首先将预先放置的本地图片加载到图片的位置上,然后SDWebImageManager开始根据URL处理图片,先从内存的图片缓存中去查询是否有图片,没有再生成一个从硬盘缓存查找图片的任务添加到队列,就算硬盘缓存存在图片也需要先将图片添加到内存缓存中,当然可能存在内存缓存的空余内存不够的情况,必须先清空内存缓存然后在将图片回调到控制器进行展示。当然如果硬盘缓存里也没有图片,就必须通过NSURLConnection来下载图片,下载完成后自然是异步进行图片解码。将解码的图片回调到需要的地方进行展示。同时将图片保存到内存缓存和硬盘缓存,需要注意的是,将图片写入硬盘缓存也要异步进行,避免拖慢主线程。更重要的是,SDImageCache在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片