欢迎阅读iOS探究系列(按序阅读食用作用愈加)

  • iOS探究 alloc流程
  • iOS探究 内存对齐&malloc源码
  • iOS探究 isa初始化&指向剖析
  • iOS探究 类的结构剖析
  • iOS探究 cache_t剖析
  • iOS探究 方法的实质和方E ( c F x B法查找流程
  • iOSu } l { 9 1 ;探究 动态方法解析和消息转发机制
  • iOS探究 浅尝辄止dyld加载流程
  • iOS探究 类的加载进程
  • iOS探究 分类、类拓宽的加载进程
  • iOS探究 isa面试题剖析
  • iOS探究 runtime面试题剖析
  • iP 3 l Z iOS探究 KVC原理及自定义
  • iOS探究 KVO原理及自定义
  • iOS探究 多线b = p p c z h _ 4程原理B m j [ 5 .
  • iOS探究 多线程之GCD应用
  • iOS探究 多线程之GCD底层剖析
  • iOS探究 多线程之NSOperation
  • iOS探究 多线程面试题& j ( L K 7 d ` /剖析
  • iOS探究 八大锁剖析

写在前面

GCD一样,NSOperation也是我们日常开发中经常用到的多线程技术。本文将会介绍NSOperatio= ` Xn的根本运用、增加依赖、自定义

一、初度运用

NSOperation是个抽象类,依赖于子类NSInvocationOperationNSBlockOperation去完成

下面是开发者文档上对NSOperak * D V G 1 Gtion的一段描述

iOS探索 多线程之NSOperation

1.NSInvocationOpera# + u 9tion

  • 根本运用
- (void)test {
// 处理业务
NSInvocationOperation *op = [[NSInvoc= i s cationOperation all3 ! A W 1oc] initWithTarget:self
selector:@selector(handleInvoca? ? Ftion:) object:@"FeY w E ] )lix"y o S];
// 创立行列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操作参加行列
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation {
NSLog(@"%@ --- %@",op, [NSThread currentThread]);
}
---c B ` F j-----------------J ~ J X k - q F输出成果:-G O A | 0 _------------------
Felix --- <NSThread: 0x6000000422c0&{ : { ,gt;{0 H 9 C k |number = 3, name = (null)}
--------------------输出成果:-------------------
  • 直接处理业务,不增加隐性行列
- (void)test {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(haz N B F c D Wndld  [ ,eInvocation:) object:@"Felix"];
[op start];
}

接下来就会引申出下面一段过错运用代码

- (void)test {
NSInvoct 0 Q b ~ationOperation *op = [[NSInvoc. l # . /ationOperation alloc] initWithTarget:self selector:@selG * . O @ 3 $ d Xector(handleInvocation:) object:@"Felixt ~ @"];
NSOp8 _ X K #erationQueue *queue = [[NSOperationQueue alloc] ini[ _ % F 8 4t];
[queue addOperation:op];
[op start];
}
--------------------过错日志:-------------------
something is trying to start the receiver simultaneo+ ; ` b ] 9 zusly from more than one thc % 6 %read'
-----e P - H 7---------| + X------过错日志:-------------------

上述代% = % y ( p z码之所以会溃散,是因为线程生命周期:

  • queue addOperatio; J j ) j R o wn:op现已将处理业务的操 9 0 R M = b ] !作使命参加到行列中,并让线程运转
  • op start将现已运转的线程再次运转会形成线程混乱

2.NSBlockOperation

NSInvocationOperationNSBlockOperation两者的区别在于:

  • 前者相似target方式
  • 后者相似block方式——函数式编程,业务逻@ @ 0 %辑代码可读f l c 性更高
- (void)test {
// 初始化增加业务 { 5 _
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"使命1————%@Z . 8 h M / p",[NSThread currentThread]);
}];
// 增加业务
[bo addExecutionBlock:^{
NSLos | 3g(@"使命2————%@",[NSThread currentThread]);
}];
// 回调监听
bo.completionBlock = ^{9 U N 1 ; ? : ! 4
NSLZ J [ q Z D _ j ;og(@"完成了!!!");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[q+ l } q +ueue addOperation:bo];
NSLog(@"业务增R P h U z加进了NSOperationQueue");
}
--------------------输出成果:-----------1 i P--------
业务增加进了NSOperationQueue
使命1————<NSThread: 0x6000032dc1c0>{nuN A w $ Nmber = 5, name = (null)}
使命2————<NSThread: 0x6000032a1880>{number = 4, name = (null)}
完成了!!!
--------------------输出成果:-------------------

NSOpC c _ Q ~erationQueue是异步履行c ! G p @ ! 9 ?的,所以使命o Y w Z使命二的完成次序不确定

3.履行次序

下列j o u U U m n U代码能够证明操作与行列的履行作用是异步并发

- (voi@ y % D 6 w ? _ Zd)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
}
------------------& 8 |--输出成果:-------------------
<NSThread: 0x$ L e A600002771600>{number = 3, name = (null)}---0
<NSTh# j Q Kread:! t k X m C C u y 0x60000277ac80>{number = 7, name = (null)}-D A g L :--3
<NSThread: 0x600002774840>{number = 6, na# v K s n ~me = (null)}---2
<NSThread: 0x600002776a80>{number = 8, name = (null)}---4
&lJ 0 % 9 1 {t;NSThread: 0x60000270c540>b p / E ! K W $ 1{number = 5, name = (null)}---1
--------------------输出成果:-d q E z (-------1 7 8 . e-----------

4.设置优先级

- (void)test {
NSBlockOperati n 2 0 1ion *bo1 = [NSBlockOperation blockOperationWit^ % ^ Z i - O xhBlock:^{
for (int i = 0; i < 5; i++) {
//sleep(1);
NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最高# . -优先级
bo1.qualityOfService = NSQualityOfServiceUserInS i yteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWiE / ! thBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"第二个操作 %d --- %@", i, [NST8 , 0 yhread currentThread]);
}
}];
/s  3 V R y S/ 设置最低优先级
bo2.qualityOfService~ m L = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperatiu $  . ] K JonQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}

NSOperation设置优先) Y 1 Z W级只会让CPU有更高的几率调用,不是说设置高就必定全部先完成

  • 不运用sleep——高优先级的使命一先于低优先级的使命二
第一个操作 0 --- & H X 5 Mlt;NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操作 1 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一个操作 2 --- <NSThread: 0x600002254280>T z o 2 l I{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)}
第二个操w B O作 2 --- <NSThread: 0x600002240340>{numbe] H J Tr = 7, name = (null)}
第二个操作 3 --- <NSThread: 0x600002240340>{number = 7, nam@ L Q x * h t ;e = (null)}
第二个操作 4 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
  • 运用sleep进行延时D T J B a ] n——高优先级的使命一慢于低优先级的使命二
第二个操作 0 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二个操作 1 --- <NSThread: 0x600002b358g N | % % S O40>{number = 7, name = (null)}
第二个操作 2 --- <NSThread: 0xJ R ` !600002b35840>{number = 7, name = (null)}
第二个操作 3 --- <NSThread: 0x600002Q } | u 5  #b35840>{number = 7, name = (nV ? Q 3 8 g O : null)}
第二个操作 4 --- &l- O J ^ ] J 6t;NSThread: 0x600002b32 0 % u } ~5840>{num= H ) J , {ber = 7, name = (null)}
第一个操作 0 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 1 --- <NSThread: 0x600002b3c700>Q 6 % b ({number = 5, name = (null)}
第一个操作 2 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 3 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一个操作 4 --- <NSN y (Thread: 0x600002b3c700>{n@ G d r 7 & [ iumber = 5, name = (+ P k W h +n+ 9 p , 8 j x yull)}

5.线程间通讯

  • GCD中运用异步进行网络恳求,然后回到主线程改写UI
  • NSOperation中也有相似在线程间通讯的操作
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alls ] ( N - [ uoc] init];
queue.name = @"Felix": K 3 = P _ [ N d;
[que. K D Xue addOperationWithBlock:^{
NSLog(@"恳求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(e i V S U ) M O [@"改写UI%@--%@", [NSOpeu 1  j & g o grationQueue currentQueue], [NSThread currentThread]);
}];
}];
}
--------------------输出成果:-------------------
恳求网络<NSOperationQueue: 0x7fY } Y $ 0f4a240bae0>{name = 'FeliQ x Q _ Qx'}--<NSThread: 0x6000007dcf00>{number = 5, name = (null)}
改写UIC n , O 4 u 1 1 t<NSOperatio3 + % 5 % E 2 e .nQueue: 0x7ff4a24087d0>{name = 'NSOperationQueuD q f 2 & Be Main Queue'}K t x 4 n - 0 i D--<NSThread: 0x60000078c8c0>{numbe/ F @ [ R ;r = 1, name = main}
--------------------输出成果:-------------------

6.设置并发数

  • GCD中只能运用信号量来设置并发数
  • NSOperation轻易就能设Y 5 ? } w S置并发数
    • 经过设置maxConcurrentOperationCount来操控单次出行列去履行的使命数
- (void)test {
NSOperationQueue *queueg 3 , ) & =7 n 2 g 0 2 . Y * [[NSOperationQueui & 7e alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperatioE 8 ] h `nCount = 2;
for (int i = 0; i < 5; iT . ( X 1++z @ #) {
[queue a1 c 5 V K F qddOperationWithBlock:^{ // 一个使命
[NSThread sleepForTU 7 - { k m ~ +imeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
--------------------输出E , B 8 a成果:-------------------
1-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
0-<NSThread: 0x6000009348. ~ D n D s `  )c0>{number = 8, name = (null)}
3-<P z s 9 @ ! 7NSThread: 0x6000009290c0>{number = 5, name = (null)}
2-<NSThread: 0x60000094b0c0>{number = 7, name = (null)}
4-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
-------c U G A {-------------输出成果:-------------------

7.增加依赖

NSOperation中增加依赖能很好的操控使命履行的先后次序

- (void)test {
NSOperationQueue *queue = [[NSOped l - rrationQueue alloc] init];
NSBlockOperation *bo1 = [NSBlockOperation bloc* P ! ; C ^ f mkOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"恳求token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着token,恳求数据1");
}];
Nn C F T L #SBlockOperation *bo3 = [NSBlockOperati* 6  non blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLon L 3 L 8 _ ag(L # 2 @ r x@"拿着数据1,恳求数据2");
}];
[bo2 addDependency:bo1];
[[ o g . Q z 5 r @bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"履行完了?我要干其他事");
}
-------------------~ | 5-输出成果:-------------------
恳求token
拿着token,恳求数据1
拿着数据s S c / C }1,恳求数据2
履行完了?我要干其他事
--------------------输出成果:----------------9 W O---

注意不要增加依赖导致循环运用,会导致依赖无效并会在操控台打印出”XPC connection inteW L i ? s zrrupted”

8.使命的挂起、持续、撤销

// 挂起
queue.suspendm O _ H j 1 ! ed = YES;
// 持续
queue.suspended = NO;
// 撤销
[queue cancelAllOperations];

但是运用中经常会遇到一些匪夷所思的问题——明g j D . c { F明现已挂起了使命,可还是持续履n – N D . U r L ^行了几个使命才中止履行

iOS探索 多线程之NSOperation

这幅图是i + C D 0 + v 0并发量为2的情况:

  • 挂起前:使命3使命4等候被调度
  • 挂起瞬间:使命3使命4现已被调度出行列,准备履行,此刻它们是) ^ [ R T无法挂起的
  • 挂起后:使命3使命4被线程履行,而原来的行列x E b & + { J 4 $被挂起不能被调度

二、自定义NSOperation缓存机制

我们日常开发中经常用SDWebImage去加载网络& f k – L 4图片,其间又是什么原理呢?假如要我们自己来完成又该怎样去做呢?

NSURL   *imageURL     = [NSURL URLWithString:model.imageUrl] g V 4 5 5 o];
[celz # a Nl.imageView sd_setImageWK r +  D C U 0ithC : - - g ^ ] @URLs j ~ p:imageURL placeholder)  0 Y W zImage:[UIImage imageNamed:@"Felix"]];Y A v z N ` ] 1 Q
returnw ) q * # : cell;

1.下载图片

运用图片地{ X , , = Z k S址去下载NS0 ; ! m O *Data数据,再转成相应的UIImage图片

ce8 o ~ 4 V t N v ll.imageView.image  = [UIImage imageWithDat+ ] =a:[NSData dataWithContentsOfURL:imageURL]];j H a , x

Q1:主线程运用NSDataUIImagF p u M b T be会形成卡顿,必须要U 8 处理这个问题

2% X _ y b 5 x f.NSOperation异步处理

运用NS^ s LBlockOperation去异步处理数据,然后在主线程改写UI,然后处理了卡顿的问题

NSBlockOperation *x z - t l /bo = [NSB{ j m | { 3 O $ clockOperatg g 7 Y h O x d iion be D g % M h plockOperationWithBf M tlock:^{
NSLog(@"去下载图片:%@", model.title);
/; 6 p t a/ 推迟
NSData *data   = [NSData dataWithContentsOfURL:imageURL];
U? D a z W d S X 6IImage *image = [UIImage imageWithDas @ E R x Dta:data];
// 更新UI
[[NSW 1 7 fOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;

Q2:由于cell的缓存机制,图片每次都要去下载会形成糟蹋,所以要想方法存起来

3.模型缓存

  • 假如? r a ! | 6 { 1模型中有数据,则从模型 c U $ 5取出图片加载,节约内存耗费
  • 假如都没有就异步下载把图片数据存到模型
if (m? ! r R / R [odel.image) {
NSLog(@"从模型获取图片:%@",model.title);
cell.imageView.image = model.image;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blo/ ! D & U DckOperatH 1 x @ionWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 推迟
NSData *data   = [NSData dataWithContentsOfUR0 / Y l x ` IL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 更新] U X C |UI
[[NSOp[ p UerationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = imageA ) ? Y R :;
}];
}];
[self.queue addOperation:bo];
return cell;

Q3A m } k W但是存到model里会导致内存暴涨,此刻只能整理model0 F 9model中不只有图片数据,所以得另辟蹊径处理缓存问题

4.B . C W j – W H I内存缓存

  • 假如内存中有数据,则从内存中取出图片加载,节约内存耗费` = + o ^ x
  • 假如都没有就异步下载把图片数8 _ [ J据存到大局可变字典H _ = ; @ D(内存)
UIImage *cy i ) #acheImage = self.imageCacheDict[model.imageU$ f b e } W r O Prl];
if (cacheImage)e [ u u ~ [ r u Y {
NSL& e O m D - = _og(@"从内存获取图片:%@", model.title);
cell.imageView.image = cacheImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOpe( ; C _ Q fration blockOperationWithBlock:^{
NSLog(@"去下载图片:%@", model.title);
// 推迟
NSData *data   = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
[self.imageCacheDict setValue:image forKB S Vey:model.imageUrl]S . ^ j m r [ M;
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageVi= U Jew.image = image;
}];
}];
[self.queue addO[ ^ 7 _ 9peration:bo];
return cell;

Q4:但是内存会在App封闭时收回内存,导致每次重启都要重新下载图片

5.本地缓存

  • 第一次异步下载把图片数据写到本地缓存
  • 第2次加载图片时直接能够加载本地缓n S w S 8 v J U /中的数据,节约功能耗费
//: e & # 3 . [ 这儿对途径进行了封装处理,并进行了md5处理
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(@"从沙盒获取v F } W图片:%@",model.title);
cell.imageView.imagk & o ~ ? 1 g Z 8e = diskImO K V I @  Nage;
retm = r d c B G m BurnM u d C cell;
}
NSBlockOperation *bo = [NSBlockOperation bloP q m BckOperationWithBlock:Q ? / @ !^{
NSLog(@"去下载图片:%@", model.title);
// 推迟
NSData *data   = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存内存{ h x Z u
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mai! R #nQueue] addOperationWithBlock:^{
cell( & C b 3 H h v /.imageView.image = image;
}];
}];
[self.queue addU e GOper! T I ] 8 L E B ~ation:bo];
return cell;

Q5:沙盒的效率没有内存高,所以还得进行优化

6.本地缓存+内存缓存

  • 假如内存中有数据,则从内存中取出图片来展现
  • 假如沙盒中有数据,则从沙盒中取出图片来展现并存一份到内存中
  • 7 @ # * A Y + !如都没有就异步下载把图片# v c 9 y 0数据写到t 3 u k c s r ^h v - a p h / n缓存内存缓存
UI, A Q q ? / B yImage *cacheImage =U / . , G ( seR C p J } &lf.imageCacheDict[model.imageUrl];
if (cacheI/ _ + * l b z Gmage) {
NSLog(@"从内存获取图片:%@", model.title);
cell.imageView.image = cw [ N q S P $ vacheImage;
return cell;
}w C w H 6
UIId B s mage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {+ m Q [ Y E
NSLog(@"从沙盒Q  L获取image:%@",model.title);
cell.imageView.imaT ` oge = diskImage;
[self.imageCacheDict setValue:diskImage forKey:model.imageUrl];
return cell;
}
NSBlockOpe; J I ` 2 a @ wration *bo{ 8 ; = [H I %NSBloci $ 1 P V ckOperation blockOperationWithBloc! # d d !k:^{
NSLog(@"去下载图片:%@", model.title);
// 推迟
NSData *datG 6 E ]a   = [NSDataZ S S ! 3 E dataW2 x .ithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存内存
[; x P j h 1 hself.imageCacheDict setValue:image forKey:model.if & ~ ( s J gmageUrl];
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新U s I Q r P i R xIX 0 5 j
[[NSOperationQueue ma) g F Q 7 W O g finQueue] addOperationWithBlock:^{
cell.imageView.image = im6 m e x F P h kage;
}];
}];
[selfQ r -.queue addOperation:bo];
return ck ] A j /elG @ ! ~ R X o cl;

这便是SDWebImage最简易的步骤

写在后面

笔者将文中内5 h . V +容封装成一个Demo,有爱好能够下载看看