玩转iOS开发 - 多线程开发

时间:2021-05-04 00:35:23

前言

本文主要介绍iOS多线程开发中使用的主要技术:NSOperation, GCD。 NSThread, pthread。 内容依照开发中的优先推荐使用的顺序进行介绍,涉及多线程底层知识比較多的NSThread, pthread 放到了后面。建议小伙伴们先看文件夹。依据自己的需求来阅读。

NSOperation

简单介绍

使用NSOperation和NSOperationQueue能简单高效的实现多线程编程

NSOperation和NSOperationQueue实现多线程的详细步骤:

(1)先将须要运行的操作封装到一个NSOperation对象中

(2)然后将NSOperation对象加入到NSOperationQueue中

(3)系统会⾃动将NSOperationQueue中的NSOperation取出来

(4)将取出的NSOperation封装的操作放到⼀条新线程中执⾏

NSOperation的子类

NSOperation是个抽象类,并不具备封装操作的能力,必须使⽤它的子类

(1)不能直接使用(方法没有实现)

(2)约束子类都具有共同的属性和方法

使用NSOperation⼦类的方式有3种:

(1)NSInvocationOperation

(2)NSBlockOperation

(3)自己定义子类继承NSOperation,实现内部对应的⽅法

NSInvocationOperation

创建NSInvocationOperation 对象

    - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

调用start 方法開始运行操作

- (void)start;

一旦运行操作,就会调用target 的 selector 方法

注意:

默认情况下,调用start方法后并不会开一条新线程去运行操作,而是当前线程同步运行操作;仅仅有将NSOperation 加入到NSOperationQuene中。才会异步运行操作;

1. 运行操作

//创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@"fileName"]; //在当前线 //程运行方法(開始运行操作)
[op start];

2. 把操作加入到队列(并開始异步运行)

//创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@"fileName"]; //将操作加入到队列,会自己主动异步调用方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op]; - (void)downloadFile:(id)object
{
NSLog(@"下载:%@----线程:%@",object,[NSThread currentThread]);
}

3. 开启多个线程,不会顺序运行

我们要记住:

NSOperation是对GCD的封装。而GCD并发队列,异步运行。

//队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++)
{
//创建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(downloadFile:) object:@(i)]; //将操作加入到队列,会自己主动异步调用方法
[queue addOperation:op];
} - (void)downloadFile:(id)object
{
NSLog(@"下载:%@----线程:%@",object,[NSThread currentThread]);
}

注意:

默认情况下。假设操作没有放到队列中queue中,都是同步运行。仅仅有将NSOperation放到一个NSOperationQueue中,才会异步运行操作

加入操作到NSOperationQueue中

- (void)addNOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void(^)(void))block

NSBlockOperation

创建NSBlockOperation对象

+ (id)blockOperationWithBlock:(void)^(void)

通过addExecutionBlock 方法加入很多其它的操作

- (id)addExecutionBlock:(void)(^)(void)block

注意:仅仅要在NSBlockOperation封装的操作数>1, 就会一步运行操作;

1. NSBlockOperation

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

for (int i = 0; i < 10; i++)
{
//创建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"down %d %@",i,[NSThread currentThread]);
}]; //把操作加入到队列中
[queue addOperation:op];
}

2. NSOperationQueue加入block的operation

以下我们直接把操作的block代码块加到队列中,是不是代码更简洁啦,block是不是用起来非常爽-_-

NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 

for (int i = 0; i < 10; i++)
{
[queue addOperationWithBlock:^{
NSLog(@"down %d %@",i,[NSThread currentThread]);
}];
}

3. 全局操作队列

以下我们定义一个全局队列来调度全部的异步操作

@property (nonatomic, strong) NSOperationQueue *queue; 

//懒载入队列
- (NSOperationQueue *)queue
{
if (_queue == nil)
{
_queue = [[NSOperationQueue alloc] init];
} return _queue;
} for (int i = 0; i < 10; i++)
{
[self.queue addOperationWithBlock:^{
NSLog(@"down %d %@",i,[NSThread currentThread]);
}];
}

4. 监听操作完毕

[op1 setCompletionBlock:^{
NSLog(@".....");
}];

并发数

  • 并发数:同一时候执⾏行的任务数.比方,同一时候开3个线程运行3个任务,并发数就是3
  • 最大并发数:同一时间最多仅仅能运行的任务的个数。

最⼤大并发数的相关⽅方法

- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

注意:

  • 假设没有设置最大并发数。那么并发的个数是由系统内存和CPU决定的,可能内存多就会开多一点。内存少就开少一点。

  • num的值并不代表线程的个数,仅仅代表线程的ID。

  • 最大并发数不要乱写(5以内)。不要开太多。一般以2~3为宜,由于尽管任务是在子线程进行处理的,可是cpu处理这些过多的子线程可能会影响UI,让UI变卡。

- (void)demo
{
self.queue.maxConcurrentOperationCount = 3; for (int i=1; i<50; i++)
{
[self.queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"====%@===%d",[NSThread currentThread],i);
}]; [NSThread sleepForTimeInterval:1.0];
}
}

不加最大并发数:

  • 此时会创建非常多线程,线程数越多说明线程池更大了。

    可是线程越多越耗资源,分配线程的时间也就越多。所以使用线程的时候要合适最好

  • GCD通常仅仅会开启5~6个线程

  • 通过设置sleepForTimeInterval能够延迟线程运行时间,也能够降低线程数。可是一来于操作的运行时间。

加入最大并发数:

  • 把操作加入到队列
[ self.queue addOperationWithBlock]
  • 去线程池去取空暇的线程,假设没有就创建线程

  • 把操作交给从线程池中取出的线程运行

  • 运行完毕后,把线程再放回线程池中

  • 反复2,3,4直到全部的操作都运行完

队列的取消,暂停和恢复

取消队列的全部操作

- (void)cancelAllOperations;

提⽰:

也能够调用NSOperation的 - (void)cancel;⽅法取消单个操作

暂停和恢复队列

- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列

- (BOOL)isSuspended; //当前状态

暂停和恢复的适用场合

在tableview界面,开线程下载远程的网络界面,对UI会有影响。使用户体验变差。那么这样的情况。就能够设置在用户操作UI(如滚动屏幕)的时候。暂停队列(不是取消队列),停止滚动的时候,恢复队列。

演示样例代码-摇奖机

玩转iOS开发 - 多线程开发

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *lbl0;
@property (weak, nonatomic) IBOutlet UILabel *lbl1;
@property (weak, nonatomic) IBOutlet UILabel *lbl2; @property (weak, nonatomic) IBOutlet UIButton *btn;
//全局队列
@property (nonatomic, strong) NSOperationQueue *queue; - (IBAction)start:(UIButton *)sender;
@end @implementation ViewController //队列懒载入-> 假设没有初始话queue, 则不会有队列生成,程序不会有响应
- (NSOperationQueue *)queue
{
if (_queue == nil)
{
_queue = [[NSOperationQueue alloc] init];
}
return _queue;
} - (void)viewDidLoad
{
[super viewDidLoad];
} - (IBAction)start:(UIButton *)sender
{
//首先要推断队列是否为空->推断队列是否是挂起状态时,并不会推断队列中是否有操作
if(self.queue.operationCount == 0)
{
//假设操作为空,说明运行摇奖,同一时候開始button变暂停
[self.queue addOperationWithBlock:^{
[self random];
}];
[self.btn setTitle:@"停止" forState:UIControlStateNormal];
//当前队列运行。不被挂起
self.queue.suspended = NO; }
else if(self.queue.isSuspended)
{
//假设当前是挂起状态->開始队列->btn:暂停
self.queue.suspended = NO;
[self.btn setTitle:@"停止" forState:UIControlStateNormal];
}
else
{
//假设当前是运行状态->暂停队列->btn:開始
self.queue.suspended = YES;
[self.btn setTitle:@"開始" forState:UIControlStateNormal];
} } - (void)random
{
//假设单签队列没有挂起
while (![NSOperationQueue currentQueue].isSuspended)
{
int num = arc4random_uniform(10);
int num1 = arc4random_uniform(10);
int num2 = arc4random_uniform(10); [NSThread sleepForTimeInterval:0.05]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.lbl0.text = [NSString stringWithFormat:@"%d",num];
self.lbl1.text = [NSString stringWithFormat:@"%d",num1];
self.lbl2.text = [NSString stringWithFormat:@"%d",num2];
}];
}
}
@end

操作优先级

设置NSOperation在queue中的优先级,能够改变操作的执⾏优先级

- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;

优先级的取值

  • NSOperationQueuePriorityVeryLow = -8L,

  • NSOperationQueuePriorityLow = -4L,

  • NSOperationQueuePriorityNormal = 0,

  • NSOperationQueuePriorityHigh = 4,

  • NSOperationQueuePriorityVeryHigh = 8

说明:优先级高的任务,调用的几率会更大。

操作依赖

NSOperation之间能够设置依赖来保证运行顺序。⽐如一定要让操作A运行完后,才干运行操作B:

[opB addDependency:opA];

能够在不同queue的NSOperation之间创建依赖关系

注意:

不能循环依赖(不能A依赖于B,B又依赖于A)

1. 模拟软件的部分升级

/*=======依赖关系========*/
/**
*模拟软件的部分升级:下载->解压->通知用户升级
*/
//下载压缩包-> 操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@",[NSThread currentThread]);
}]; //解压,拷贝到对应文件夹
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"解压 %@",[NSThread currentThread]);
}]; //通知用户
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户升级完毕 %@",[NSThread currentThread]);
}]; //设置操作的依赖关系
[op2 addDependency:op1];
[op3 addDependency:op2]; //加入操作
/**
*waitUntilFinished YES 等待全部的操作运行完毕 会堵塞窗口的运行
*waitUntilFinished NO 不等待
*/
[self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
NSLog(@"over");

循环依赖

发生循环依赖,程序不会死锁。界面也不会堵塞,操作不会运行

[op2 addDependency:op1];

[op3 addDependency:op2];

[op1 addDependency:op3];

3. 依赖关系能够跨队列运行

[op2 addDependency:op1];
[op3 addDependency:op2]; //子队列
[self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
NSLog(@"over"); [[NSOperationQueue mainQueue] addOperation:op3];

提示

任务加入的顺序并不能够决定运行顺序,运行的顺序取决于依赖。

使用Operation的目的就是为了让开发者不再关心线程。

操作的监听

能够监听一个操作的运行完毕:

- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
- (void)demo
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i=1; i<3; i++)
{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@==download image = %d",[NSThread currentThread], i);
}
}]; [self.queue addOperation:op]; op.completionBlock =^{
NSLog(@"finishing download image, do ot
her things");
}; NSLog(@"%@====I'm later than downloading image",[NSThread currentThread]);
}

注意:

  • 下载图片的操作在子线程中运行,op仅仅是加入到队列里了。至于什么时候运行须要等待对应的线程;

  • 因此第三个输出出如今最前面。且在主线程中。

  • completionBlock中的操作要等待下载图片完毕。

GCD和NSOperation的比較

GCD

  • GCD是iOS4.0推出的 ,主要针对多核cpu做了优化,是C语言的技术
  • GCD是将任务(block)加入到队列(串行/并行/全局/主队列),而且以同步/异步的方式运行任务的函数

GCD提供了一些NSOperation不具备的功能

  • 一次性运行
  • 延迟运行
  • 调度组

NSOperation

  • NSOperation是iOS2.0推出的,iOS4之后重写了NSOperation
  • NSOperation将操作(异步的任务)加入到队列(并发队列),就会运行指定操作的函数

NSOperation里提供的方便的操作 :

  • 最大并发数
  • 队列的暂停/继续
  • 取消全部的操作
  • 指定操作之间的依赖关系(GCD能够用同步实现)

困了,待续,最近更新……

GCD

NSThread

Pthread

玩转iOS开发 - 多线程开发的更多相关文章

  1. iOS之多线程开发NSThread、NSOperation、GCD

    原文出处: 容芳志的博客   欢迎分享原创到伯乐头条 简介iOS有三种多线程编程的技术,分别是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全称:Grand Centr ...

  2. (IOS)多线程开发

    一.线程的使用 以向网络请求一张图片为例 -(void)downURL:(NSURL *)aURL { NSData *d = [NSData dataWithContentsOfURL:aURL]; ...

  3. iOS 开发--多线程

    前面在<Bison眼中的iOS开发多线程是这样的(二)>一文中讲完了多线程的NSThread,不难发现这种方式的多线程实现起来非常的复杂,为了简化多线程的开发,iOS提供了GCD来实现多线 ...

  4. iOS开发--多线程

    前面在<Bison眼中的iOS开发多线程是这样的(二)>一文中讲完了多线程的NSThread,不难发现这种方式的多线程实现起来非常的复杂,为了简化多线程的开发,iOS提供了GCD来实现多线 ...

  5. iOS开发多线程篇—多线程简单介绍

    iOS开发多线程篇—多线程简单介绍 一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcod ...

  6. iOS开发多线程篇—线程安全

    iOS开发多线程篇—线程安全 一.多线程的安全隐患 资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象.同一个变量.同一个文件 当多个线程访问同一块 ...

  7. iOS多线程开发资源抢夺和线程间的通讯问题

    说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题.举例来说,每年春节都是一票难 ...

  8. iOS多线程开发

    概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的,一个复杂的多步操 ...

  9. iOS开发——多线程篇——多线程介绍

    一.进程和线程1.什么是进程进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开迅雷.Xcode,系统就会分别启动2个进程 通过“活动监 ...

随机推荐

  1. TypeScript - 基本类型系统

    对于程序来说我们需要基本的数据单元,如:numbers, strings, structures, boolean 等数据结构.在TypeScript中我们支持很多你所期望在JavaScript中所拥 ...

  2. Jstat在分析java的内存GC时的应用

    jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量.使用时,需加上查看进程的进程id,和所选参数. 执行:cd $JAVA_HOME/bin中执行jstat,注意j ...

  3. PHP二维数组根据某个键名排序

    $result = array( array(           "amount": "11.00",           "date": ...

  4. 用c&num;读取并分析sql2005日志

    用过logExplorer的朋友都会被他强悍的功能吸引,我写过一篇详细的操作文档可以参考http://blog.csdn.net/jinjazz/archive/2008/05/19/2459692. ...

  5. python-获取URL中的json数据

    数据源为某系统提供的URL,打开是json文件,python代码获取如下: URL替换成自己的即可. import urllib.request def get_record(url): resp = ...

  6. vim&colon; 基本知识;

    1. 函数: function!   funcName(para.) content; endfunction 如果添加!,将覆盖已存在的重名函数: 注: 该博文为扩展型: 2.调用外部命令: exe ...

  7. HDU 1043 Eight(八数码)

    HDU 1043 Eight(八数码) 00 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)   Problem Descr ...

  8. 六、在U-boot中让LCD填充纯色

    1. 编译U-boot 准备好U-boot压缩包urbetter-u-boot-1.1.6-v1.0.tgz,输入命令:tar -xvf urbetter-u-boot-1.1.6-v1.0.tgz ...

  9. 软件工程github使用小结

    1.在 https://github.com/join 这个网址处申请注册一个Github账号,申请成功后可在https://github.com/login 处利用刚刚注册的账号进行登录,才能开始在 ...

  10. adnanh webhook 框架request values 说明

      request values 在adnanh webhook 是比较重要的,规则触发以及命令参数传递都是通过它 支持的request values 类似 http header 查询参数 play ...