欢迎阅读iOS探索系列(按序阅读食用效果更加)git
- iOS探索 alloc流程
- iOS探索 内存对齐&malloc源码
- iOS探索 isa初始化&指向分析
- iOS探索 类的结构分析
- iOS探索 cache_t分析
- iOS探索 方法的本质和方法查找流程
- iOS探索 动态方法解析和消息转发机制
- iOS探索 浅尝辄止dyld加载流程
- iOS探索 类的加载过程
- iOS探索 分类、类拓展的加载过程
- iOS探索 isa面试题分析
- iOS探索 runtime面试题分析
- iOS探索 KVC原理及自定义
- iOS探索 KVO原理及自定义
- iOS探索 多线程原理
- iOS探索 多线程之GCD应用
- iOS探索 多线程之GCD底层分析
- iOS探索 多线程之NSOperation
- iOS探索 多线程面试题分析
- iOS探索 八大锁分析
与GCD
同样,NSOperation
也是咱们平常开发中常常用到的多线程技术。本文将会介绍NSOperation
的基本使用、添加依赖、自定义github
NSOperation
是个抽象类,依赖于子类NSInvocationOperation
、NSBlockOperation
去实现面试
下面是开发者文档上对NSOperation
的一段描述 编程
- (void)test {
// 处理事务
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(handleInvocation:) object:@"Felix"];
// 建立队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操做加入队列
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation {
NSLog(@"%@ --- %@",op, [NSThread currentThread]);
}
--------------------输出结果:-------------------
Felix --- <NSThread: 0x6000000422c0>{number = 3, name = (null)}
--------------------输出结果:-------------------
复制代码
- (void)test {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];
[op start];
}
复制代码
接下来就会引伸出下面一段错误使用代码缓存
- (void)test {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
[op start];
}
--------------------错误日志:-------------------
something is trying to start the receiver simultaneously from more than one thread'
--------------------错误日志:-------------------
复制代码
上述代码之因此会崩溃,是由于线程生命周期:bash
queue addOperation:op
已经将处理事务的操做任务加入到队列中,并让线程运行op start
将已经运行的线程再次运行会形成线程混乱NSInvocationOperation
和NSBlockOperation
二者的区别在于:网络
target
形式block
形式——函数式编程,业务逻辑代码可读性更高- (void)test {
// 初始化添加事务
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1————%@",[NSThread currentThread]);
}];
// 添加事务
[bo addExecutionBlock:^{
NSLog(@"任务2————%@",[NSThread currentThread]);
}];
// 回调监听
bo.completionBlock = ^{
NSLog(@"完成了!!!");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@"事务添加进了NSOperationQueue");
}
--------------------输出结果:-------------------
事务添加进了NSOperationQueue
任务1————<NSThread: 0x6000032dc1c0>{number = 5, name = (null)}
任务2————<NSThread: 0x6000032a1880>{number = 4, name = (null)}
完成了!!!
--------------------输出结果:-------------------
复制代码
NSOperationQueue
是异步执行的,因此任务一
、任务二
的完成顺序不肯定多线程
下列代码能够证实操做与队列的执行效果是异步并发
的并发
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
}
--------------------输出结果:-------------------
<NSThread: 0x600002771600>{number = 3, name = (null)}---0
<NSThread: 0x60000277ac80>{number = 7, name = (null)}---3
<NSThread: 0x600002774840>{number = 6, name = (null)}---2
<NSThread: 0x600002776a80>{number = 8, name = (null)}---4
<NSThread: 0x60000270c540>{number = 5, name = (null)}---1
--------------------输出结果:-------------------
复制代码
- (void)test {
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
//sleep(1);
NSLog(@"第一个操做 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最高优先级
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"第二个操做 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最低优先级
bo2.qualityOfService = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}
复制代码
NSOperation
设置优先级只会让CPU有更高的概率调用,不是说设置高就必定所有先完成异步
sleep
——高优先级的任务一
先于低优先级的任务二
第一个操做 0 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操做 1 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操做 2 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操做 3 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操做 4 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第二个操做 0 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操做 1 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操做 2 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操做 3 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二个操做 4 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
复制代码
sleep
进行延时——高优先级的任务一
慢于低优先级的任务二
第二个操做 0 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操做 1 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操做 2 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操做 3 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操做 4 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第一个操做 0 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操做 1 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操做 2 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操做 3 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操做 4 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
复制代码
GCD
中使用异步进行网络请求,而后回到主线程刷新UINSOperation
中也有相似在线程间通信的操做- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
[queue addOperationWithBlock:^{
NSLog(@"请求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
}];
}];
}
--------------------输出结果:-------------------
请求网络<NSOperationQueue: 0x7ff4a240bae0>{name = 'Felix'}--<NSThread: 0x6000007dcf00>{number = 5, name = (null)}
刷新UI<NSOperationQueue: 0x7ff4a24087d0>{name = 'NSOperationQueue Main Queue'}--<NSThread: 0x60000078c8c0>{number = 1, name = main}
--------------------输出结果:-------------------
复制代码
GCD
中只能使用信号量来设置并发数NSOperation
轻易就能设置并发数
maxConcurrentOperationCount
来控制单次出队列去执行的任务数- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{ // 一个任务
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
--------------------输出结果:-------------------
1-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
0-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
3-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
2-<NSThread: 0x60000094b0c0>{number = 7, name = (null)}
4-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
--------------------输出结果:-------------------
复制代码
在NSOperation
中添加依赖能很好的控制任务执行的前后顺序
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"请求token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着token,请求数据1");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着数据1,请求数据2");
}];
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"执行完了?我要干其余事");
}
--------------------输出结果:-------------------
请求token
拿着token,请求数据1
拿着数据1,请求数据2
执行完了?我要干其余事
--------------------输出结果:-------------------
复制代码
注意不要添加依赖致使循环运用,会致使依赖无效并会在控制台打印出"XPC connection interrupted"
// 挂起
queue.suspended = YES;
// 继续
queue.suspended = NO;
// 取消
[queue cancelAllOperations];
复制代码
可是使用中常常会遇到一些匪夷所思的问题——明明已经挂起了任务,可仍是继续执行了几个任务才中止执行
任务3
、任务4
等待被调度任务3
、任务4
已经被调度出队列,准备执行,此时它们是没法挂起的任务3
、任务4
被线程执行,而原来的队列被挂起不能被调度咱们平常开发中常常用SDWebImage
去加载网络图片,其中又是什么原理呢?若是要咱们本身来实现又该怎么去作呢?
NSURL *imageURL = [NSURL URLWithString:model.imageUrl];
[cell.imageView sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"Felix"]];
return cell;
复制代码
使用图片地址去下载NSData
数据,再转成相应的UIImage
图片
cell.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
复制代码
Q1:主线程使用NSData
转UIImage
会形成卡顿,必需要解决这个问题
使用NSBlockOperation
去异步处理数据,而后在主线程刷新UI,从而解决了卡顿的问题
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 延迟
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
复制代码
Q2:因为cell的缓存机制,图片每次都要去下载会形成浪费,因此要想办法存起来
模型
中有数据,则从模型
中取出图片加载,节约内存消耗模型
中if (model.image) {
NSLog(@"从模型获取图片:%@",model.title);
cell.imageView.image = model.image;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 延迟
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
复制代码
Q3:可是存到model
里会致使内存暴涨,此时只能清理model
,但model
中不仅有图片数据,因此得另辟蹊径处理缓存问题
内存
中有数据,则从内存
中取出图片加载,节约内存消耗全局可变字典(内存)
中UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(@"从内存获取图片:%@", model.title);
cell.imageView.image = cacheImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 延迟
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
[self.imageCacheDict setValue:image forKey:model.imageUrl];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
复制代码
Q4:可是内存会在App关闭时回收内存,致使每次重启都要从新下载图片
本地缓存
中本地缓存
中的数据,节约性能消耗// 这里对路径进行了封装处理,并进行了md5处理
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(@"从沙盒获取图片:%@",model.title);
cell.imageView.image = diskImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 延迟
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存内存
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
复制代码
Q5:沙盒的效率没有内存高,因此还得进行优化
内存
中有数据,则从内存
中取出图片来展现沙盒
中有数据,则从沙盒
中取出图片来展现并存一份到内存中本地缓存
和内存缓存
中UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(@"从内存获取图片:%@", model.title);
cell.imageView.image = cacheImage;
return cell;
}
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(@"从沙盒获取image:%@",model.title);
cell.imageView.image = diskImage;
[self.imageCacheDict setValue:diskImage forKey:model.imageUrl];
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 延迟
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存内存
[self.imageCacheDict setValue:image forKey:model.imageUrl];
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
复制代码
这就是
SDWebImage
最简易的步骤
笔者将文中内容封装成一个Demo,有兴趣能够下载看看