上一篇写到了相册功能的实现,现在问题基本上也都解决了,对于一个程序员的必经之路就是什么都尝试了一遍才能知道最好用的方法,接下来回归正题,这篇文章准备对iOS的多线程开发做一个总结。
进程与线程
进程:指在系统中正在运行的一个应用程序(每个进程都是独立的,都运行在自己专用并且受到保护的内存空间之内)
例如同时在运行的QQ和微信就属于两个独立的进程
线程:线程是进程的基本单元,一个进程的所有任务都在线程中执行
(一个进程想要执行任务,必须要有线程每个进程至少有一条线程)
例如迅雷下载文件就是在线程中执行
iOS多线程
iOS的每个进程启动之后都会建立一个主线程,一般所有的UI任务都放在主线程中执行,现在也可以使用其他线程来进行UI的更新,所以接下来介绍一下iOS多线程的操作。iOS现在常用的多线程开发有一下三个方式:
NSThread
GCD
NSOperation
NSThread
NSThread是一个轻量级的多线程开发,但是需要自行管理生命周期
NSThread的使用
NSThread的创建:
//实例方法创建
-(id)initWithTarget:(id)target selector:@selector(doSomeThing:) object:(id)argument
//类方法创建
+(void)detachNewThreadSelector:@selector(doSomeThing:) toTarget:(id)target withObject:(id)argument
//selector:线程执行的方法,这个selector只能有一个参数,而且不能有返回值
//target: selector消息发送的对象
//argument:传输给target的唯一参数,也可以是nil
//隐式创建
[self performSelectorInBackground:@selector(doSomeThing:) withObject:nil];
获取当前线程:
NSThread *current = [NSThread currentThread];
获取主线程:
NSThread *main = [NSThread mainThread];
暂停当前线程:
// 暂停2s
//方法一:
[NSThread sleepForTimeInterval:2];
//方法二:
NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date];
线程之间的通信:
在指定线程上执行操作:
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
在主线程上执行操作:
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
在当前线程上执行操作:
[self performSelector:@selector(run) withObject:nil];
以上是NSThread的主要用法,它一定的优缺点,优点就是它比较轻量级,可以直观的控制线程对象。但是缺点也很明显,就是它需要自己来管理线程生命周期,线程同步对数据的加锁操作会有一定的系统资源开销。
NSThread暂时介绍到这几种简单的应用方法,
因为会有涉及到RunLoop的概念,会在后续进行更加详细的分析,RunLoop可以先看一下这篇,讲的比较详细:RunLoop
接下来主要介绍的是经常会用到的也很重要的:
GCD
首先GCD的全称是Grand Central Dispatch,是Apple的libdispatch库的一个名称,使用GCD可以达到很多你所需要的结果,因为GCD本身是苹果公司为多核的并行运算提出的解决方案,它是基于C语言的,完全由系统来管理线程,我们不需要去编写线程代码,只用定义想执行的任务,而且能够提供给我们更大的控制力和大量的底层函数,可以极为简单的在不同代码之间传递上下文,减少上下文的切换,提高系统资源利用效率,减轻系统的负载。
在说GCD之前还有一些东西是要搞清楚的:
任务:
任务就是你需要做的事情,在GCD中一般就是一个block。
任务有两种执行方式:同步执行和异步执行。
同步执行就是每次只有一个任务被执行,异步执行就是在同一时间可以有多个任务被执行。同步执行操作会阻塞当前的线程,它需要等待当前block中的任务执行完毕之后,当前线程才会继续向下运行。异步执行不会阻塞当前线程,它会直接向下执行。
队列:
队列使用于存放任务的,有三种队列:串行队列和并行队列以及Main dispatch queue
串行队列:
串行队列是把任务按FIFO(先进先出)的顺序来执行,也就是说GCD会将任务按先进先出的顺序来取出一个,执行一个然后再执行下一个,一个一个的执行。
并行队列:
并行队列虽然可以同时执行多个任务,但其本质还是按照FIFO的顺序来执行,但是它取出一个任务会放到一个新的线程中执行,它会根据系统负载来选择并发执行这些任务,也就是说如果任务比较多,系统是不会同时执行所有任务的。
Main dispatch queue:
Main dispatch queue与主线程的功能是相同的,其实相当于一个串行队列,可以在主线程中进行UI的更新。
创建队列:
主队列
//主要用于刷新UI
dispatch_queue_t mainQueue = dispatch_get_main_queue();
获取一个全局队列:
dispatch_queue_t globalQueue = dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
long identifier:对应以上四个值,指该队列的优先级,从上到下依次为高优先级,默认优先级,低优先级以及后台优先级,最后一个是后台级别的队列,它会等待所有比它优先级高的队列中的任务完成后或者CPU在空闲的时候才会执行它的任务
unsigned long flags:苹果官方文档是这样解释的: Flags that are reserved for future use。标记是为了未来使用保留的!所以这个参数应该永远指定为0
创建串行队列或并行队列:
//创建串行队列:
dispatch_queue_t serieQueue1 = dispatch_queue_create("mySerieQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serieQueue2 = dispatch_queue_create("mySerieQueue", DISPATCH_QUEUE_SERIAL);
//创建并行队列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("myConCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
参数一:标识符,用于debug的时候标识唯一的队列,可以为空
参数二:表示创建的队列是串行的还是并行的,DISPATCH_QUEUE_SERIAL与NULL表示串行的,DISPATCH_QUEUE_CONCURRENT表示并行的。
添加任务到队列中:
同步任务:
dispatch_sync(serieQueue1, ^{
NSLog(@"我是任务1");
});
dispatch_sync(serieQueue1, ^{
NSLog(@"我是任务2");
});
dispatch_sync(serieQueue1, ^{
NSLog(@"我是任务3");
});
异步任务:
dispatch_async(conCurrentQueue, ^{
NSLog(@"我是任务1");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"我是任务2");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"我是任务3");
});
在这里可以尝试原本上述的四种情况,打印结果如下:
添加同步任务至串行队列:
添加同步任务至并行队列:
共计运行三次,三次任务都是顺序执行
添加异步任务至串行队列:
共计运行三次,三次任务都是顺序执行
添加异步任务至并行队列:
共计运行三次,三次任务不一定按照顺序执行,谁先好先执行谁
队列组:
队列组可以将很多队列放到一个组里,当组里的任务执行完毕后,会通过一个方法通知我们。
//创建队列组
dispatch_group_t myGroup = dispatch_group_create();
//创建队列
dispatch_queue_t myGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//添加任务至队列组中,多个任务使用异步方法
//执行6次循环
dispatch_group_async(myGroup, myGlobalQueue, ^{
for (int i = 0; i < 6; i++) {
NSLog(@"group - 01 - %d",i);
}
});
//主队列执行10次循环
dispatch_group_async(myGroup, dispatch_get_main_queue(), ^{
for (int i = 0; i < 10; i++) {
NSLog(@"main - 02 - %d",i);
}
});
dispatch_group_async(myGroup, myGlobalQueue, ^{
for (int i = 0; i < 20; i++) {
NSLog(@"group - 03 - %d",i);
}
});
//执行完毕后通知
dispatch_group_notify(myGroup, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@",[NSThread currentThread]);
});
打印结果:
这个功能比较实用,可以把很多任务放在一个组里,并在任务结束后发送通知。
上述就是GCD的常用的方法,过后会有更加详细的讲解,包括死锁的问题。
接下来就是NSOperation和NSOperationQueue了,我今天在实践的时候发现这个东西有很大的可操作性,简单来说就是,同一个任务,使用GCD的话,代码量简洁,并且可读性很高,但是缺点就是无法控制这个任务的进度,也就是说你无法控制线程的取消等,但是使用NSOperation是可以的,可操作性很强,所以暂时不在这里进行讲解,下一篇将会对NSOperation和NSOperationQueue进行详细的分享。
总结
本篇文档只是做了NSThread与GCD的简单使用的分享,具体的深入研究将会在后续更新进行专题分享,如果有写的不对或者不好的地方欢迎大家在评论区留言,我会进行相对应的修改,同时也希望有这方面经验的大神可以互相交流一下,大家不打赏的话点一下喜欢也是可以的,我会继续更新。