------------------- NSOperation综合案例: 1.项目简介/UI 搭建 ----------------------------*/
重点: 1.搭建 UI 界面; 2.构建数据模型; 3.缓存开发中需要用到的数据模型; 4.注意在懒加载方法中,不要出现点语法.
{
1. 搭建 UI 界面;
// 导航控制器 + 表格视图控制器(根控制器)
2. 构建数据模型;
// 注意提供一个字典转模型的方法.
// KVC(Key - Value - Code)键值编码使用注意;
// 注意与 KVO(Key - Value - Observe)键值监听的区别; 存放
3. 将数据模型缓存到可变数组中.开发的数据直接来源于这个可变数组. "apps -------> 数据模型"
// 懒加载存放数据模型的数组.操作步骤: 存放
(1)将 apps.plist 文件转换为数组(数组中存放的是数据字典): array ---------> 字典模型
{
// <1> 获得 apps.plist 文件 的路径(知道了文件路径,就能够找到文件):
{
// 获取 apps.plist 文件名的全路径
NSString *path =[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
}
// <2> 根据 apps.plist文件 转换为数组
{
// 根据 apps.plist文件 转换为数组; array 中存储的是数据字典
NSArray *array = [NSArray arrayWithContentsOfFile:path];
}
}
(2)取出数据字典,将数据字典转换为模型,并且将模型存放在apps数组中:
{
// 取出数据字典,将数据字典转换为模型
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// array: 存放字典模型;
// obj: 数组中存放的字典;
// idx: obj在数组中的位置
// <1>取出数组中的数据字典
NSDictionary *dict = obj;
// <2>将数据字典转换为数据模型
ITApp *app = [ITApp ITAppWithDictionary:dict];
// 将数据模型添加到临时可变数组中.
[appsArray addObject:app];
}];
// 将数据模型存放在 apps 中
_apps = appsArray;
}
// 注意,取出数组中的字典的两种方式:<1> for 循环; <2>Block.推荐使用第二种.效率更高.
#pragma 懒加载
// 存放数据模型的数组
-(NSMutableArray *)apps
{
if (!_apps) {
_apps = [NSMutableArray array];
// 获取 apps.plist 文件名的全路径
NSString *path =[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
// 根据 apps.plist文件 转换为数组; array 中存储的是数据字典
NSArray *array = [NSArray arrayWithContentsOfFile:path];
// NSLog(@"%@",array);
// 定义一个临时的可变数组来存放数据模型;
NSMutableArray *appsArray = [NSMutableArray array];
// 拿出数据字典,将数据字典转换为模型,并且将模型存放在apps数组中:
//两种方法:
//<1> for 循环
// for (int i = 0 ; i < array.count; i ++) {
//
// // 取出 array 中存放的数据字典
// NSDictionary *dict = array[i];
//
// // 将数据字典转换为数据模型
// ITApp *app = [ITApp ITAppWithDictionary:dict];
//
// // 将数据模型添加到临时可变数组中.
// [appsArray addObject:app];
// };
//
// <2> Block
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// obj:数组中存放的字典; idx: obj在数组中的位置
NSDictionary *dict = obj;
// 将数据字典转换为数据模型
ITApp *app = [ITApp ITAppWithDictionary:dict];
// 将数据模型添加到临时可变数组中.
[appsArray addObject:app];
}];
// 将数据模型存放在 apps 中
_apps = appsArray;
}
return _apps;
}
4. 注意点: UITableView的数据源方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
必须返回一个 :UITableViewCell;
// 调试的时候,用下面这种方式写,不会报错.
return [UITableViewCell new];
}
}
/*--------------------------- NSOperation综合案例: 2.下载图片/技术选择 ----------------------------*/
重点: 1.分析项目需求; 2.处理内存警告,优化用户体验; 3.技术选择,技术点实施,代码编写. 4.Bug?
{
项目需求:
<1> 下载图片;
分析:
1>.子线程下载图片,主线程显示图片.
2>.开启子线程有三种技术方案可供选择: (1)NSThread ,(2)GCD ,(3)NSOperation 配合 NSOperationQueue使用.
<2> 内存警告处理;
分析:
接收到内存警告的时候,停止一切下载操作,防止闪退.
<3> 用户体验;
分析:
在与用户做 UI 交互的时候,最好暂停图片的下载;用户滚动结束之后,再继续下载图片.
1. 技术选择 : (3)NSOperation 配合 NSOperationQueue使用.
2. 技术点实施:
// <1>用户开始滚动的时候,暂停下载操作;停止滚动之后,恢复下载操作.
{
#pragma UIScrollViewDelegate
// 开始滚动的时候调用
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(@"暂停下载---");
// 暂停所有下载操作
[self.queue setSuspended:YES];
}
// 滚动结束的时候调用
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
NSLog(@"恢复下载---");
// 恢复所有下载操作
[self.queue setSuspended:NO];
}
}
// <2>接收到内存警告的时候,取消一切操作.
{
// 接收到内存警告的时候调用
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// 取消一切下载操作
[self.queue cancelAllOperations];
}
}
// <3>将下载图片的操作封装在 NSBlockOperation.最后将操作放在并发队列中.自动执行!
{
__weak typeof(self) wself = self;
// 定义下载操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 下载网络图片
UIImage *webImage = [wself downloadWebImage:app.icon];
//回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 显示图片
cell.imageView.image = webImage;
}];
}];
// 将下载操作放在并发队列中.自动开启下载.
[self.queue addOperation:op];
}
3.运行程序之后,发现 3 个Bug:
<1> 程序运行之后,图片不会直接显示出来.需要刷新之后才能显示(滚动/点击都会重绘 UI).
<2> 图片错位问题.
<3> 用户体验方面的Bug:只要滚动,图片就会重复下载.即使已经下载好的图片,也会重新下载.(耗费流量,体验巨差,巨耗电).
}
/*------------------------------ NSOperation综合案例: 3.Bug 解决 ---------------------------------*/
重点: 1.分析Bug产生的原因并解决Bug. 2.知识点回顾:如何防止一个url对应的图片重复下载?
{
Bug 产生的原因分析:
<1> "程序运行之后,图片不会直接显示出来.需要刷新之后才能显示(滚动/点击都会重绘 UI)."
Bug产生原因: UITableViewCell 中刚开始没有设置显示图片的 Frame,也就是说没有 Frame.图片下载完后,点击/刷新之后,就会重新绘制 UITableViewCell,这样就会显示图片了.
解决 Bug :下载之前最好先把图片的 Frame 绘制出来.比如,可以添加一张占位图片.
{
// 设置占位图片.
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
}
<2> "图片错位问题."
Bug产生原因: UITableViewCell 的重用以及网络下载延时产生的.
解决 Bug :让数据控制视图(视图根据数据来显示),设置一个图片缓存,cell 中的图片来源于这个图片缓存.
{
<1>.设置图片缓存.
定义一个字典做为图片缓存,保存下载好的图片(以图片的 url 作为 key 值;以下载好的图片为 Value).
// 可以选择 NSCache 代替字典作为缓存机制.
// NSCache在内存紧张的时候,会自动释放一些资源(自动销毁图片,我们无法控制).
// 如果使用字典,在接收到内存警告之后,需要手动释放资源.
<2>.从缓存中取出 cell 对应的图片.
cell 设置图片:根据 cell 的 app.icon(url)从字典中取出对应的图片.
<3>.图片下载完毕之后刷新所在行的数据.
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
注意: 没必要刷新整个表格视图,刷新所在行的数据就可以了.
}
<3> "用户体验方面的Bug:只要滚动,图片就会重复下载.即使已经下载好的图片,也会重新下载.(耗费流量,体验巨差,巨耗电)."
Bug产生原因: 只要上下滚动,就会调用 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 这个方法.这个方法频繁调用导致下载操作一直创建.
解决 Bug :每次创建下载操作之前,最好先检查一下载操作是否存在,对于已经存在的操作,不要重复下载.
{
<1>.设置操作缓存.
定义一个字典作为下载操作缓存,保存已经创建好的下载操作(同样,以图片的 url 为 key值;以操作Operation为 Value).
<2>.从缓存中取出操作比较.
每次重新创建下载操作之前,首先从下载操作缓存之中查看操作是否已经存在.如果存在,就不要再次创建;如果不存在,创建新的下载操作.
<3>.防止操作缓存越来越大
图片下载成功之后,就下载操作就没有存在的意义了,应该及时清除缓存,防止下载操作缓存越来越大.
}
补充知识点:NSCache
{
// 可以选择 NSCache 代替字典作为缓存机制.
NSCache类结合了各种自动删除策略,以确保不会占用过多的系统内存.如果其它应用需要内存时,系统自动执行这些策略.
NSCache是线程安全的,我们可以在不同的线程中添加/删除和查询缓存中的对象,而不需要锁定缓存区域.
}
2. 问题:如何防止一个url对应的图片重复下载?
答:cell下载图片思路 - 无沙盒缓存;
}
/*------------------------ NSOperation综合案例: 4.完善项目-添加沙盒缓存 -----------------------------*/
重点: 1.处理内存警告. 2.添加沙盒缓存.
{
1.由于添加了内存缓存机制(图片缓存和操作缓存),在接收到内存警告的时候,最好释放内存缓存.
将图片缓存在沙盒中,可以优化用户体验.以后每次展示图片,对于已经下载过的图片,就不需要重新下载.
2.添加沙盒缓存.
<1> 认识沙盒:
默认情况下,每个沙盒含有3个文件夹:Documents, Library 和 tmp.
Documents:苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录.
Library:存储程序的默认设置或其它状态信息;
// Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除
tmp:提供一个即时创建临时文件的地方.
<2> 将图片写入沙盒:将文件存储在 Library/Caches路径下
1>.获取 ~/Caches路径.
{
// 得到 Caches 路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
}
2>.拼接文件路径.
{
// 拼接文件路径( path + app.icon.lastPathComponent ),以url 中图片的名作为名字.
NSString *file = [path stringByAppendingPathComponent:app.icon.lastPathComponent];
}
3>.将 image 转换为 二进制数据. //沙盒中不能直接存取图片数据.
{
// 将 UIImage 图片转换为二进制数据
NSData *data = UIImagePNGRepresentation(webImage);
}
4>.将图片的二进制数据存入沙盒.
{
// 将图片的二进制数据存入沙盒,路径为:file
[data writeToFile:file atomically:YES];
}
<3> 从沙盒中获取图片:
1>.获取 ~/Caches路径.
2>.拼接完整的图片文件路径.
3>.根据完整的图片文件路径获取图片.
{
UIImage *image = [UIImage imageWithContentsOfFile:fileName];
}
}
/*----------------------------- NSOperation综合案例: 5.SDWebImage使用 ------------------------------*/
重点: 1.了解 SDWebImage.
{
1.SDWebImage:
SDWebImage是一个开源的第三方库,它提供了UIImageView的一个分类,以支持从远程服务器下载并缓存图片的功能.
<1> SDWebImageManager
在实际的运用中,我们并不直接使用SDWebImageDownloader类及SDImageCache类来执行图片的下载及缓存.
为了方便用户的使用,SDWebImage提供了SDWebImageManager对象来管理图片的下载与缓存.
我们经常用到的诸如UIImageView+WebCache等控件的分类都是基于SDWebImageManager对象的.
该对象将一个下载器和一个图片缓存绑定在一起,并对外提供两个只读属性来获取它们.
<2> UIImageView+WebCache
我们在使用SDWebImage的时候,使用得最多的是UIImageView+WebCache中的针对UIImageView的扩展方法,这些扩展方法将UIImageView与WebCache集成在一起,来让UIImageView对象拥有异步下载和缓存远程图片的能力.
其中最核心的方法是 -sd_setImageWithURL:placeholderImage:options:progress:completed:,其使用SDWebImageManager单例对象下载并缓存图片,完成后将图片赋值给UIImageView对象的image属性,以使图片显示出来.
2.面试题
1> SDWebImage的默认缓存是多长时间?
* 1个星期
2> SDWebImage的默认最大并发数是多少?
* 6
3> SDWebImage底层是怎么实现的?
* cell下载图片思路 – 有沙盒缓存
3.SDWebImage常用方法:
1> 常用方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
2> SDWebImageOptions
* SDWebImageRetryFailed : 下载失败后,会自动重新下载
* SDWebImageLowPriority : 当正在进行UI交互时,自动暂停内部的一些下载操作
* SDWebImageRetryFailed | SDWebImageLowPriority : 拥有上面2个功能
3> 内存处理:当app接收到内存警告时
/**
* 当app接收到内存警告
*/
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
SDWebImageManager *mgr = [SDWebImageManager sharedManager];
// 1.取消正在下载的操作
[mgr cancelAll];
// 2.清除内存缓存
[mgr.imageCache clearMemory];
}
}
/*----------------------------- NSOperation综合案例: 6.自定义 NSOperation ----------------------------*/
{
这一块知识点比较复杂,难以理解.只需要知道以下几点:
自定义 NSOperation的步骤:
* 重写 -(void)main 方法,在里面实现想执行的操作.
重写 -(void)main 方法注意点:
1> 自己创建自动释放池(如果异步操作,无法访问主线程的自动释放池).
2> 经常通过 -(BOOL)isCancelled 方法检测操作是否取消,对取消做出响应.
// 如果真的想搞这块,还需要掌握‘不同对象间‘通信的的方法:(通知/代理/Block 等).
// 详细见代码
}