IOS高级编程之三:IOS 多线程编程

多线程的概念在各个操做系统上都会接触到,windows、Linux、mac os等等这些经常使用的操做系统,都支持多线程的概念。java

固然ios中也不例外,可是线程的运行节点多是咱们日常不太注意的。ios

例如:程序员

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4     for(int i = 0 ; i < 100 ; i++)
 5     {
 6         NSLog(@"===%@===%d" , [NSThread currentThread].name , i);
 7         if(i == 20)
 8         {
 9             // 建立线程对象
10             NSThread *thread = [[NSThread alloc]initWithTarget:self
11                 selector:@selector(run) object:nil];
12             // 启动新线程
13             [thread start];
14 //            // 建立并启动新线程
15 //            [NSThread detachNewThreadSelector:@selector(run) toTarget:self
16 //                withObject:nil];
17         }
18     }
19 }
20 - (void)run
21 {
22     for(int i = 0 ; i < 100 ; i++)
23     {
24         NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
25     }
26 }

上面打印的内容每一次都是不一样的,什么意思呢?编程

当咱们建立了4个线程后,加上UI主线程一共5个线程。windows

新的线程在执行start方法以后,并不会当即执行。他们会被cpu随机的执行,只是间隔很是短,以致于咱们感受上是多个线程在同时执行。网络

因此线程有这么一个特色:执行的随机性多线程

可是咱们能够设置线程的优先级,让优先级更高的线程得到更多的执行机会。并发

 

那么何时要使用多线程编程呢?app

相信有过开发经验的程序员都知道,当咱们把代码写完后,程序是一行一行逐行执行代码的,当其中一行代码须要执行较长时间(例如select一个教复杂的语句或者较多的数据时),那么程序就会出现卡顿的现象,不会相应用户的操做。异步

由于开启程序后会默认开启一个主线程,即UI线程。当处于刚才那种状况时,好比一个windows程序,就会出现程序暂时无响应的提示,好像电脑卡主的感受,这是很是很差的一种感觉。。。。

当咱们要避免这种状况的时候,最好的方式就是多线程,开启一个新的线程,用来执行一个耗时的操做,执行完成后再让主线程来修改ui页面(若是须要的话)。

 

介绍完了线程的一些知识,那么下面来具体看ios中多线程的几种实现方式,主要有一下三种:

一、NSThread :就是刚刚例子中使用的方式,可是使用上比较繁琐,并且须要控制好数据的同步和异步问题

二、NSOperation 和 NSOperationQueue : 这种方式代码比较简洁,可读性强,并且使用队列的形式管理多个任务,本人比较喜欢

三、使用GCD( Grand Central Dispatch ) :相较于NSThread使用简单,使用队列管理任务

 

1、首先来介绍NSThread

一、建立NSThread的两种方式

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

+(void)detachNewThreadSelector:(SEL) selector toTarget:(id) target withObject:(id) arg:

第二种方式,建立NSThread后会自动启动

 

二、NSThread的经常使用方法

+currentThread : 返回当前正在执行的线程对象

 

三、线程的状态

一开始的例子中提了一下,线程建立后,执行了start方法并非当即就执行了。可能ui线程执行了几毫秒后,cpu才执行它,执行几毫秒后再执行ui线程,但这个过程是随机发生的。

若是想让线程当即执行,那么可让ui线程sleep 1毫秒,这样cpu就会执行其余可执行的线程,能够达到当即执行的效果

1 [NSThread sleepForTimeInterval:0.001];//让当前运行的线程睡眠1毫秒

线程正在执行时,调用isExecuting方法返回 YES ,线程执行完成后调用 isFinished 方法就会返回 YES

 

四、终止子线程

线程会以一下3种方式之一结束,结束后就处于死亡状态

1)线程执行的方法体执行完成,线程正常结束

2)执行过程当中出现了错误

3)调用NSThread 类的 exit 方法来终止当前线程

 

在UI 线程中 ,NSThread 并无提供方法来结束其余的子线程。可是咱们能够利用 NSThread 的cancel 方法,执行该方法后, 该线程的状态为 isCancelled = YES,但并不会结束线程。

 1 NSThread* thread;
 2 - (void)viewDidLoad
 3 {
 4     [super viewDidLoad];
 5     // 建立新线程对象
 6     thread = [[NSThread alloc] initWithTarget:self selector:@selector(run)
 7         object:nil];
 8     // 启动新线程
 9     [thread start];
10 }
11 - (void)run
12 {
13     for(int i = 0 ; i < 100 ; i++)
14     {
15         if([NSThread currentThread].isCancelled)
16         {
17             // 终止当前正在执行的线程
18             [NSThread exit];
19         }
20         NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
21         // 每执行一次,线程暂停0.5秒
22         [NSThread sleepForTimeInterval:0.5];
23     }
24 }
25 - (IBAction)cancelThread:(id)sender
26 {
27     // 取消thread线程,调用该方法后,thread的isCancelled方法将会返回NO
28     [thread cancel]; 
29 }

利用例子中代码的形式,咱们就能够达到在UI线程中结束其余子线程的目的了。

 

五、线程睡眠

要让线程进入阻塞状态或者睡眠状态,能够执行sleepXXX格式的方法:

+(void) sleepUntilDate:(NSDate *) aDate  : 让线程睡眠,知道aDate那个时间点再醒过来

-(void)sleepForTimeInterval :让线程睡眠多少秒

 

六、改变线程优先级

NSThread 提供了以下几个方法来获取和设置线程的优先级

+threadPriority: 获取当前正在执行的线程的优先级

-threadPriority:获取线程实例的优先级

 

+setThreadPriority :(double) priority : 设置当前正在执行的线程的优先级

-setThreadPriority :(double) priority : 设置线程实例的优先级

(double) priority的 取值范围是0.0~1.0;优先级越高的线程得到的执行机会越多

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4     NSLog(@"UI线程的优先级为:%g" , [NSThread threadPriority]);
 5     // 建立第一个线程对象
 6     NSThread* thread1 = [[NSThread alloc]
 7         initWithTarget:self selector:@selector(run) object:nil];
 8     // 设置第一个线程对象的名字
 9     thread1.name = @"线程A";
10     NSLog(@"线程A的优先级为:%g" , thread1.threadPriority);
11     // 设置使用最低优先级
12     thread1.threadPriority = 0.0;
13     // 建立第二个线程对象
14     NSThread* thread2 = [[NSThread alloc]
15         initWithTarget:self selector:@selector(run) object:nil];
16     // 设置第二个线程对象的名字
17     thread2.name = @"线程B";
18     NSLog(@"线程B的优先级为:%g" , thread2.threadPriority);
19     // 设置使用最高优先级
20     thread2.threadPriority = 1.0;
21     // 启动2个线程
22     [thread1 start];
23     [thread2 start];
24 }
25 - (void)run
26 {
27     for(int i = 0 ; i < 100 ; i++)
28     {
29         NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
30     }
31 }

 

2、使用GCD实现多线程

GCD简化了多线程的实现,主要有两个核心概念:

一、队列:队列负责管理开发者提交的任务,以先进先出的方式来处理任务。

1)串行队列:每次只执行一个任务,当前一个任务执行完成后才执行下一个任务

2)并行队列:多个任务并发执行,因此先执行的任务可能最后才完成(由于具体的执行过程致使)

二、任务:任务就是开发者提供给队列的工做单元,这些任务将会提交给队列底层维护的线程池,所以这些任务将会以多线程的方式执行。

 

三、建立队列

1)获取系统默认的全局并发队列:

1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2) 获取系统主线程关联的穿行队列

1 dispatch_queue_t queue = dispatch_get_main_queue();

若是将任务提交给主线程关联的串行队列,那么就至关于在程序主线程中去执行该任务。

3)建立穿行队列

1 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);

4)建立并发队列

1 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);

5)获取当前执行代码所在队列

dispatch_get_current_queue,返回一个dispatch_queue_t类型的值

 

四、提交任务

使用下面的方法将任务以同步或者异步的方式提交到队列

 1 //将代码块以异步的方式提交给指定队列
 2 void  dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
 3 
 4 //将函数以异步的方式提交给指定队列,通常执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
 5 void  dispatch_async_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
 6 
 7 //将代码块以同步的方式提交给指定队列
 8 void  dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
 9 
10 //将函数以同步的方式提交给指定队列,通常执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
11 void  dispatch_sync_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
 1 //将代码块以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 以后执行
 2 void  dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
 3 
 4 //将函数以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 以后执行
 5 void  dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue,  void* context, dispatch_function_t work);
 6 
 7 //将代码块以异步的方式提交给指定队列,队列的线程池将会重复屡次执行该任务
 8 void  dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t));
 9 
10 //将函数以异步的方式提交给指定队列,队列的线程池将会重复屡次执行该任务
11 void  dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void* context, void(*work)(void*, size_t));
12 
13 //将代码块提交给指定队列,在应用的某个生命周期内金执行一次
14 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

 

下面给出一个以异步方式向串行队列、并发队列添加任务的实例

 1 // 定义2个队列
 2 dispatch_queue_t serialQueue;
 3 dispatch_queue_t concurrentQueue;
 4 - (void)viewDidLoad
 5 {
 6     [super viewDidLoad];
 7     // 建立串行队列
 8     serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
 9     // 建立并发队列
10     concurrentQueue = dispatch_queue_create("fkjava.queue"
11         , DISPATCH_QUEUE_CONCURRENT);
12 }
13 - (IBAction)serial:(id)sender
14 {
15     // 依次将2个代码块提交给串行队列
16     // 必须等到第1个代码块完成后,才能执行第2个代码块。
17     dispatch_async(serialQueue, ^(void)
18     {
19         for (int i = 0 ; i < 100; i ++)
20         {
21             NSLog(@"%@=====%d"  , [NSThread currentThread] , i);
22         }
23     });
24     dispatch_async(serialQueue, ^(void)
25     {
26         for (int i = 0 ; i < 100; i ++)
27         {
28             NSLog(@"%@------%d" , [NSThread currentThread] , i);
29         }
30     });
31 }
32 - (IBAction)concurrent:(id)sender
33 {
34     // 依次将2个代码块提交给并发队列
35     // 两个代码块能够并发执行
36     dispatch_async(concurrentQueue, ^(void)
37     {
38         for (int i = 0 ; i < 100; i ++)
39         {
40             NSLog(@"%@=====%d"  , [NSThread currentThread] , i);
41         }
42     });
43     dispatch_async(concurrentQueue, ^(void)
44     {
45         for (int i = 0 ; i < 100; i ++)
46         {
47             NSLog(@"%@------%d" , [NSThread currentThread] , i);
48         }
49     });
50 }

提交同步任务:

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4 }
 5 - (IBAction)clicked:(id)sender
 6 {
 7     // 以同步方式前后提交2个代码块
 8     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
 9         , ^(void){
10             for (int i = 0 ; i < 100; i ++)
11             {
12                 NSLog(@"%@=====%d"  , [NSThread currentThread] , i);
13                 [NSThread sleepForTimeInterval:0.1];
14             }
15         });
16     // 必须等第一次提交的代码块执行完成后,dispatch_sync()函数才会返回,
17     // 程序才会执行到这里,才能提交第二个代码块。
18     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
19         , ^(void){
20             for (int i = 0 ; i < 100; i ++)
21             {
22                 NSLog(@"%@-----%d"  , [NSThread currentThread] , i);
23                 [NSThread sleepForTimeInterval:0.1];
24             }
25         });
26 }

 

屡次执行的任务:

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4 }
 5 - (IBAction)clicked:(id)sender
 6 {
 7     // 控制代码块执行5次
 8     dispatch_apply(5
 9         , dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
10         // time形参表明当前正在执行第几回
11         , ^(size_t time)
12         {
13             NSLog(@"===执行【%lu】次===%@" , time
14                 , [NSThread currentThread]);
15         });
16 }

 

只执行一次的任务

 1 @implementation FKViewController
 2 - (void)viewDidLoad
 3 {
 4     [super viewDidLoad];
 5 }
 6 - (IBAction)clicked:(id)sender
 7 {    
 8     static dispatch_once_t onceToken;
 9     dispatch_once(&onceToken, ^{
10         NSLog(@"==执行代码块==");
11         // 线程暂停3秒
12         [NSThread sleepForTimeInterval:3];
13     });
14 }

3、使用NSOperation 和 NSOPerationQueue 实现多线程

和GCD差很少,也是有队列和任务的概念

NSOperationQueue:表明一个先进先出的队列,负责管理系统提交的多个NSOperation。底层维护一个线程池,会按顺序启动线程来执行提交给队列的NSOperation

NSOperation:表明多线程任务。通常不直接使用NSOperation,而是使用NSOperation的子类。或者使用NSInvocationOperation和NSBlockOperation(这两个类继承自NSOperation);

一、NSOperation的使用

NSOperation 的使用相较于GCD是面向对象的,OC实现的,而GCD应该是C实现的(看函数的定义和使用)。

使用NSOperation 只需两步:

1)建立 NSOperationQueue 队列,并未该队列设置相关属性

2)建立 NSOperation 子类对象,并将该对象提交给 NSOperationQueue 队列,该队列将会按顺序依次启动每一个 NSOperation。

 

二、NSOperationQueue的经常使用方法

 1 +currentQueue //类方法,返回执行当前NSOperation的NSOperationQueue队列
 2 
 3 +mainQueue //返回系统主线程的NSOperationQueue队列
 4 
 5 -(void) addOperation:(NSOperation *) operation //将operation添加到NSOperationQueue队列中
 6 
 7 -(void) addOperations:(NSArray *) ops waitUnitlFinished:(BOLL) wait //将NSArray中包含的全部NSOperation添加到NSOperationQueue。若是第二个参数指定为YES,将会阻塞当前线程,直到提交的全部NSOperation执行完成。若是第二个参数为NO,该方法当即返回,NSArray包含的NSOperation将以异步方式执行,不会阻塞当前线程。
 8 
 9 - operations //只读属性,返回该NSOperationQueue管理的全部NSOperation
10 -operationCount //只读属性,返回该NSOperationQueue管理的全部NSOperation数量
11 
12 -cancelAllOperations: //取消NSOperationQueue队列中全部正在排队和执行的NSOperation
13 
14 -waitUntilAllOperationsAreFinished://阻塞当前线程,直到该NSOperationQueue中全部排队和执行的NSOperation执行完成再接触阻塞
15 
16 -(NSInteger) maxConcurrentOperationCount://返回该队列最大支持多少个并发线程
17 
18 -setMaxConcurrentOperationCount:(NSInteger) count //设置该队列最大支持多少个并发线程
19 
20 -setSuspended:(BOOL) suspend: //设置NSOperationQueue是否已经暂停调度正在排队的NSOperation
21 
22 -(BOLL) isSuspended: //返回NSOperationQueue是否已经暂停调度正在排队的NSOperation

三、使用NSInvocationOperation 和 NSBlockOperation

NSInvocationOperation 和 NSBlockOperation 继承自 NSOperation,因此能够直接使用,用于封装须要异步执行的任务。

 

使用它们实现图片异步下载:

 1 NSOperationQueue* queue;
 2 - (void)viewDidLoad
 3 {
 4     [super viewDidLoad];
 5     queue = [[NSOperationQueue alloc]init];
 6     // 设置该队列最多支持10条并发线程
 7     queue.maxConcurrentOperationCount = 10;
 8 }
 9 - (IBAction)clicked:(id)sender
10 {
11     NSString* url = @"http://www.......jpg";
12     // 以传入的代码块做为执行体,建立NSOperation
13     NSBlockOperation* operation = [NSBlockOperation
14         blockOperationWithBlock:^{
15             // 从网络获取数据
16             NSData *data = [[NSData alloc]
17                 initWithContentsOfURL:[NSURL URLWithString:url]];
18             // 将网络数据初始化为UIImage对象
19             UIImage *image = [[UIImage alloc]initWithData:data];
20             if(image != nil)
21             {
22                 // 在主线程中执行updateUI:方法
23                 [self performSelectorOnMainThread:@selector(updateUI:)
24                     withObject:image waitUntilDone:YES];
25             }
26             else
27             {
28                 NSLog(@"---下载图片出现错误---");
29             }
30         }];
31     // 将NSOperation添加给NSOperationQueue
32     [queue addOperation:operation];
33 }
34 -(void)updateUI:(UIImage*) image
35 {
36     self.iv.image = image;
37 }

 

 1 NSOperationQueue* queue;
 2 - (void)viewDidLoad
 3 {
 4     [super viewDidLoad];
 5     queue = [[NSOperationQueue alloc]init];
 6     // 设置该队列最多支持10条并发线程
 7     queue.maxConcurrentOperationCount = 10;
 8 }
 9 - (IBAction)clicked:(id)sender
10 {
11     NSString* url = @"http://www.......jpg";
12     // 以self的downloadImageFromURL:方法做为执行体,建立NSOperation
13     NSInvocationOperation* operation = [[NSInvocationOperation alloc]
14         initWithTarget:self selector:@selector(downloadImageFromURL:)
15         object:url];
16     // 将NSOperation添加给NSOperationQueue
17     [queue addOperation:operation];
18 }
19 
20 // 定义一个方法做为线程执行体。
21 -(void)downloadImageFromURL:(NSString *) url
22 {
23     // 从网络获取数据
24     NSData *data = [[NSData alloc]
25         initWithContentsOfURL:[NSURL URLWithString:url]];
26     // 将网络数据初始化为UIImage对象
27     UIImage *image = [[UIImage alloc]initWithData:data];
28     if(image != nil)
29     {
30         // 在主线程中执行updateUI:方法
31         [self performSelectorOnMainThread:@selector(updateUI:)
32             withObject:image waitUntilDone:YES];
33     }
34     else
35     {
36         NSLog(@"---下载图片出现错误---");
37     }
38 }
39 -(void)updateUI:(UIImage*) image
40 {
41     self.iv.image = image;
42 }

 

四、自定义NSOperation 的子类

建立 NSOperation 的子类,须要重写一个方法:-(void) main,该方法的方法体将做为 NSOperationQueue 完成的任务

下面自定义一个NSOperation 子类来实现下载图片的功能

1 @interface MyDownImageOperation : NSOperation
2 @property (nonatomic , strong) NSURL* url;
3 @property (nonatomic , weak) UIImageView* imageView;
4 - (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv;
5 @end

 

 1 @implementation MyDownImageOperation
 2 - (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv
 3 {
 4     self = [super init];
 5     if (self) {
 6         _imageView = iv;
 7         _url = url;
 8     }
 9     return self;
10 }
11 // 重写main方法,该方法将做为线程执行体
12 - (void)main
13 {    
14     // 从网络获取数据
15     NSData *data = [[NSData alloc]
16         initWithContentsOfURL:self.url];
17     // 将网络数据初始化为UIImage对象
18     UIImage *image = [[UIImage alloc]initWithData:data];
19     if(image != nil)
20     {
21         // 在主线程中执行updateUI:方法
22         [self performSelectorOnMainThread:@selector(updateUI:)
23             withObject:image waitUntilDone:YES]; //
24     }
25     else
26     {
27         NSLog(@"---下载图片出现错误---");
28     }
29 }
30 -(void)updateUI:(UIImage*) image
31 {
32     self.imageView.image = image;
33 }

 

viewController代码:

 1 NSOperationQueue* queue;
 2 - (void)viewDidLoad
 3 {
 4     [super viewDidLoad];
 5     queue = [[NSOperationQueue alloc]init];
 6     // 设置该队列最多支持10条并发线程
 7     queue.maxConcurrentOperationCount = 10;
 8 }
 9 - (IBAction)clicked:(id)sender
10 {
11     // 定义要加载的图片的URL
12     NSURL* url = [NSURL URLWithString:@"http://www.crazyit.org/logo.jpg"];
13     // 建立FKDownImageOperation对象
14     MyDownImageOperation* operation = [[MyDownImageOperation alloc]
15         initWithURL:url imageView:self.iv];
16     // 将NSOperation的子类的实例提交给NSOperationQueue
17     [queue addOperation:operation];
18 }
相关文章
相关标签/搜索