玩转iOS开发 - 多线程开发

前言

本文主要介绍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(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。

示例代码-摇奖机

这里写图片描述

#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)
    {
        //如果操作为空,说明执行摇奖,同时开始按钮变暂停
        [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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值