ios开发进阶之多线程02 NSOperation
2015-08-27 21:22
531 查看
一 单例模式
单例模式的作用可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问
从而方便地控制了实例个数,并节约系统资源
ARC中,单例模式的实现
在.m中保留一个全局的static的实例
static id _instance;
重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)
+ (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); return _instance; }
提供1个类方法让外界访问唯一的实例
+ (instancetype)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; }
实现copyWithZone:方法
- (id)copyWithZone:(struct _NSZone *)zone { return _instance; }
注意点
不要继承单例类
先创建子类永远是子类对象
线创建父类永远是父类对象
如何判断是否是ARC
#if __has_feature(objc_arc) //ARC #else //MRC #endif
二 NSOperation基本使用
NSOperation的作用配合使用NSOperation和NSOperationQueue也能实现多线程编程
NSOperation和NSOperationQueue实现多线程的具体步骤
先将需要执行的操作封装到一个NSOperation对象中
然后将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来
将取出的NSOperation封装的操作放到一条新线程中执行
NSOperation的子类
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperation子类的方式有3种
NSInvocationOperation
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
NSInvocationOperation
// 1.将操作封装到Operation中 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 2.执行封装的操作 // 如果直接执行NSInvocationOperation中的操作, 那么默认会在主线程中执行 [op1 start];
NSBlockOperation
// 1.封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1- %@", [NSThread currentThread]); }]; // 2.添加操作 [op1 addExecutionBlock:^{ NSLog(@"2- %@", [NSThread currentThread]); }]; [op1 addExecutionBlock:^{ NSLog(@"3- %@", [NSThread currentThread]); }]; // 2.执行操作 // 如果只封装了一个操作, 那么默认会在主线程中执行 // 如果封装了多个操作, 那么除了第一个操作以外, 其它的操作会在子线程中执行 [op1 start];
自定义子类继承NSOperation
自定义类继承于NSOperation, 那么需要将操作写到自定义类的main方法中
@implementation XMOperation - (void)main { NSLog(@"%s, %@", __func__,[NSThread currentThread]); }
三 NSOperationQueue
NSOperationQueue的作用NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block;
NSInvocationOperation
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.封装任务 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil]; // 3.将任务添加到队列中 // 只要将一个任务添加到alloc/init的队列中, 那么队列内部会自动调用start // 只要将一个任务添加到alloc/init的队列中, 就会开启一个新的线程执行队列 [queue addOperation:op1];
NSBlockOperation
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.封装任务 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1 = %@", [NSThread currentThread]); }]; // 3.将任务添加到队列中 [queue addOperation:op1];
另一种简洁写法:
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.将任务添加到队列中 // addOperationWithBlock方法会做两件事情 // 1.根据传入的block, 创建一个NSBlockOperation对象 // 2.将内部创建好的NSBlockOperation对象, 添加到队列中 [queue addOperationWithBlock:^{ NSLog(@"1 = %@", [NSThread currentThread]); }];
自定义子类继承NSOperation
// 1.创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.封装任务 XMGOperation *op1 = [[XMGOperation alloc] init]; // 3.将任务添加到队列中 [queue addOperation:op1];
四 最大并发数
什么是并发数:同时执行的任务数maxConcurrentOperationCount 默认等于 -1, 代表不限制, 可以创建N多线程
默认就是并发
如果想实现串行, 那么就设置maxConcurrentOperationCount = 1
注意: 最大并发数, 不能设置为0, 否则任务不会被执行
五 队列的暂停、取消、恢复
暂停-恢复不会暂停当前正在执行的任务,会暂停队列中的下一个任务
恢复任务,会从第一个未执行的任务恢复执行
// 如果是YES, 代表需要暂停 // 如果是NO ,代表恢复执行 self.queue.suspended = YES; self.queue.suspended = !self.queue.suspended;
取消
不会取消当前正在执行的任务
取消后任务不能恢复
耗时操作应该每执行一段判断一次
// 内部会调用所有任务的cancel方法 [self.queue cancelAllOperations];
六 NSOperation线程间通信
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 开启子线程下载图片 [queue addOperationWithBlock:^{ NSString *urlStr = @"http://..."; urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url = [NSURL URLWithString:urlStr]; NSData *data = [NSData dataWithContentsOfURL:url]; //生成下载好的图片 UIImage *image = [UIIMage imageWithData:data] // 回到主线程更新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }];
七 NSOperation依赖关系
依赖和监听只有被依赖的任务完成, 才会执行当前任务
可以跨队列依赖
[operationB addDependency:operationA]; // 操作B依赖于操作A
op1.completionBlock = ^{ NSLog(@"第一张图片下载完毕"); }; op2.completionBlock = ^{ NSLog(@"第二张图片下载完毕"); };
图片合成案例代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSOperationQueue *queue2 = [[NSOperationQueue alloc] init]; // queue.maxConcurrentOperationCount = 1; __block UIImage *image1 = nil; __block UIImage *image2 = nil; // 1.开启一个线程下载第一张图片 NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://www.ibayue.com/images/fileup/201411/20141103150824.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; // 2.生成下载好的图片 UIImage *image = [UIImage imageWithData:data]; image1 = image; }]; // 2.开启一个线程下载第二长图片 NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://i0.letvimg.com/lc02_yunzhuanma/201503/07/00/48/170acdff2830753f89957742b0e8f5b8_25431356/thumb/2_400_225.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; // 2.生成下载好的图片 UIImage *image = [UIImage imageWithData:data]; image2 = image; }]; // 3.开启一个线程合成图片 NSOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ UIGraphicsBeginImageContext(CGSizeMake(200, 200)); [image1 drawInRect:CGRectMake(0, 0, 100, 200)]; [image2 drawInRect:CGRectMake(100, 0, 100, 200)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // 4.回到主线程更新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"回到主线程更新UI"); self.imageView.image = newImage; }]; }]; // 监听任务是否执行完毕 op1.completionBlock = ^{ NSLog(@"第一张图片下载完毕"); }; op2.completionBlock = ^{ NSLog(@"第二张图片下载完毕"); }; // 添加依赖 // 只要添加了依赖, 那么就会等依赖的任务执行完毕, 才会执行当前任务 // 注意: // 1.添加依赖, 不能添加循环依赖 // 2.NSOperation可以跨队列添加依赖 [op3 addDependency:op1]; [op3 addDependency:op2]; // 将任务添加到队列中 [queue addOperation:op1]; [queue addOperation:op2]; [queue2 addOperation:op3]; }
八 多图片下载
字典转模型#import "XMGApp.h" @implementation XMGApp - (instancetype)initWithDict:(NSDictionary *)dict{ if (self = [super init]) { [self setValuesForKeysWithDictionary:dict]; } return self; } + (instancetype)appWithDict:(NSDictionary *)dict { return [[self alloc] initWithDict:dict]; } @end
懒加载
#pragma mark - lazy - (NSArray *)apps { if (!_apps) { // 1.从plist中加载数组 NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]; NSArray *arr = [NSArray arrayWithContentsOfFile:path]; // 2.定义数组保存转换好的模型 NSMutableArray *models = [NSMutableArray arrayWithCapacity:arr.count]; // 3.遍历数组中所有的字典, 将字典转换为模型 for (NSDictionary *dict in arr) { XMGApp *app = [XMGApp appWithDict:dict]; [models addObject:app]; } _apps = [models copy]; } return _apps; } - (NSMutableDictionary *)imageCaches { if (!_imageCaches) { _imageCaches = [NSMutableDictionary dictionary]; } return _imageCaches; }
#pragma mark - UITableViewDatasource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // NSLog(@"%s", __func__); // 1.获取cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"]; // 2.设置数据 XMGApp *app = self.apps[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download]; // 设置图片 /* 存在的问题: 1.在主线程中下载图片, 可能会阻塞主线程 2.重复下载 */ // 1.先从内存缓存中获取, 如果没有才去下载 UIImage *image = self.imageCaches[app.icon]; if (image == nil) { // 2.再从磁盘缓存中获取, 如果没有才去下载 NSString *filePath = [app.icon cacheDir]; NSData *data = [NSData dataWithContentsOfFile:filePath]; if (data == nil) { NSLog(@"下载图片"); // 内存缓存中没有值, 需要下载 NSURL *url = [NSURL URLWithString:app.icon]; data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 将下载好的图片缓存到内存缓存中 self.imageCaches[app.icon] = image; // 将下载好的图片写入到磁盘 [data writeToFile:filePath atomically:YES]; // 更新UI cell.imageView.image = image; }else { NSLog(@"使用磁盘缓存"); NSData *data = [NSData dataWithContentsOfFile:filePath]; UIImage *image = [UIImage imageWithData:data]; // 将下载好的图片缓存到内存缓存中 self.imageCaches[app.icon] = image; // 更新UI cell.imageView.image = image; } }else { NSLog(@"使用内存缓存"); // 更新UI cell.imageView.image = image; } // 3.返回cell return cell; }
目录结构
Documents
需要保存由”应用程序本身”产生的文件或者数据,例如:游戏进度、涂鸦软件的绘图
目录中的文件会被自动保存在 iCloud
注意:不要保存从网络上下载的文件,否则会无法上架!
Caches
保存临时文件,”后续需要使用”,例如:缓存图片,离线数据(地图数据)
系统不会清理 cache 目录中的文件
就要求程序开发时,”必须提供 cache 目录的清理解决方案”
Preferences
用户偏好,使用 NSUserDefault 直接读写!
如果要想数据及时写入磁盘,还需要调用一个同步方法
tmp
保存临时文件,”后续不需要使用”
tmp 目录中的文件,系统会自动清理
重新启动手机,tmp 目录会被清空
系统磁盘空间不足时,系统也会自动清理
封装获取文件路径方法
- (NSString *)cacheDir { // 1.获取cache目录 NSString *dir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; // 2.生成绝对路径 return [dir stringByAppendingPathComponent:[self lastPathComponent]]; } - (NSString *)documentDir { NSString *dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; return [dir stringByAppendingPathComponent:[self lastPathComponent]]; } - (NSString *)tmpDir { NSString *dir = NSTemporaryDirectory(); return [dir stringByAppendingPathComponent:[self lastPathComponent]]; }
优化版本:
#pragma mark - UITableViewDatasource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.apps.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // NSLog(@"%s", __func__); // 1.获取cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"]; // 2.设置数据 XMGApp *app = self.apps[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download]; cell.imageView.image = [UIImage imageNamed:@"abc"]; //开始没有图片,搞占位图片 // 设置图片 /* 存在的问题: 1.在主线程中下载图片, 可能会阻塞主线程 2.重复下载 */ // 1.先从内存缓存中获取, 如果没有才去下载 UIImage *image = self.imageCaches[app.icon]; if (image == nil) { // 2.再从磁盘缓存中获取, 如果没有才去下载 NSString *filePath = [app.icon cacheDir]; __block NSData *data = [NSData dataWithContentsOfFile:filePath]; if (data == nil) { NSLog(@"下载图片"); /* 存在的问题: 1.重复设置 2.重复下载 */ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 3.判断当前图片是否有任务正在下载 NSBlockOperation *op = self.operations[app.icon]; if (op == nil) { // 没有对应的下载任务 op = [NSBlockOperation blockOperationWithBlock:^{ // 开启子线程下载 // 内存缓存中没有值, 需要下载 NSURL *url = [NSURL URLWithString:app.icon]; data = [NSData dataWithContentsOfURL:url]; if (data == nil) { // 如果下载失败, 应该将当前图片对应的下载任务从缓存中移除 \以便于下次可以再次尝试下载 [self.operations removeObjectForKey:app.icon]; return; } UIImage *image = [UIImage imageWithData:data]; // 将下载好的图片缓存到内存缓存中 self.imageCaches[app.icon] = image; // 将下载好的图片写入到磁盘 [data writeToFile:filePath atomically:YES]; // 回到主线程更新UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"更新UI"); // 刷新指定的行,网速慢的情况下防止图片跳来跳去 [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; // 从缓存中将当前图片对应的下载任务移除 [self.operations removeObjectForKey:app.icon]; }]; }]; // 先将下载任务保存到缓存中 self.operations[app.icon] = op; // 将任务添加到队列中 [queue addOperation:op]; } }else { NSLog(@"使用磁盘缓存"); NSData *data = [NSData dataWithContentsOfFile:filePath]; UIImage *image = [UIImage imageWithData:data]; // 将下载好的图片缓存到内存缓存中 self.imageCaches[app.icon] = image; // 更新UI cell.imageView.image = image; } }else { NSLog(@"使用内存缓存"); // 更新UI cell.imageView.image = image; } // 3.返回cell return cell; }
图片下载注意点(面试4点)
重复下载问题
定义字典保存下载好的图片
磁盘缓存问题
内存没有尝试从磁盘获取
阻塞主线程问题
新建NSOperationQueue下载图片
重复设置问题
reloadRowsAtIndexPaths
逻辑1 - 从来没下载过 1.查看内存缓存是否有图片 2.查看磁盘缓存是否有图片 3.查看时候有任务正在下载当前图片 4.开启任务下载图片 5.写入磁盘 6.缓存到内存 7.移除下载操作 8.显示图片 逻辑2 - 已经下载过 1.查看内存缓存是否有图片 2.查看磁盘缓存是否有图片 3.使用磁盘缓存 4.将图片缓存到内存中 5.更新UI 逻辑3 - 已经下载过, 并且不是重新启动 1.查看内存缓存是否有图片 2.更新UI
九 SDWebImage架构
SDWebImageManagerSDImageCache
SDWebImageDownloader
SDWebImageDownloaderOperation
SDWebImage常见面试题
默认缓存时间多少
一周
缓存的地址
NSString *fullNamespace = [@”com.hackemist.SDWebImageCache.” stringByAppendingString:ns];
cleanDisk如何清理过期图片
删除早于过期日期的文件
保存文件属性以计算磁盘缓存占用空间
如果剩余磁盘缓存空间超出最大限额,再次执行清理操作,删除最早的文件
clearDisk如何清理磁盘
删除缓存目录
新建缓存目录
SDWebImage如何播放图片
取出gif中每一帧, 生成一张可动画图片
SDWebImage如何判断图片类型
判断图片二进制前8个字节
kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
多图片下载案例
- (void)viewDidLoad { [super viewDidLoad]; self.tableView.rowHeight = 150; // 直接下载一张图片 /* 第1个参数: 需要下载图片的URL 第2个参数: 下载的配置信息(例如是否需要缓存等等) 第3个参数: 下载过程中的回调 第4个参数: 下载完成后的回调 */ NSURL *url = [NSURL URLWithString:@"http://ia.topit.me/a/f9/0a/1101078939e960af9ao.jpg"]; [[SDWebImageManager sharedManager] downloadImageWithURL:url options:kNilOptions progress:^(NSInteger receivedSize, NSInteger expectedSize) { // receivedSize : 已经接受到的数据大小 // expectedSize : 需要下载的图片的总大小 NSLog(@"正在下载 %zd %zd", receivedSize, expectedSize); } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { // image : 下载好的图片 // error: 错误信息 // cacheType: 缓存的类型 // finished: 是否下载完成 // imageURL: 被下载的图片的地址 NSLog(@"下载成功 %@", image); }]; }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 1.获取cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"]; // 2.设置数据 XMGApp *app = self.apps[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download]; // 在老版本的SDWebImage中, 以下方法是没有sd_前缀的 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"abc"]]; // 3.返回cell return cell; }
相关文章推荐
- iOS中ScrollView(滚屏,引导界面,和判段是否是第一次登陆)
- iOS-点餐系统
- iOS判断设备-userInterfaceIdiom
- iOS-汤姆猫项目总结
- ios tableView 的header for section无法加载的问题
- 苹果开发文档 User Experience Starting Point for iOS
- iOS Provisioning Profile(Certificate)与Code Signing详解
- iOS 万能跳转界面方法
- ios即时通讯客户端开发之-mac上搭建openfire服务器
- ios-QQ界面(利用新浪微博方法实现,消除新浪微博重复计算的缺点)
- iOS:NSDate的主要几种时间形式
- 美食屋-ios 项目
- ios- 自定义cell总结
- IOS学习之单例的实现
- iOS百度地图在线建议api做搜索区域推荐
- 基于LBS平台的iOS开发
- iOS安全攻防越狱检测的攻与防
- IOS7 点击空白处隐藏键盘的几种方法
- iOS开发系列--地图与定位
- Safari调试iOS中的js