简介html
在软件开发中,多线程编程技术被普遍应用,相信多线程任务对咱们来讲已经再也不陌生了。有了多线程技术,咱们能够同作多个事情,而不是一个一个任务地进行。好比:前端和后台做交互、大任务(须要耗费必定的时间和资源)等等。也就是说,咱们可使用线程把占据时间长的任务放到后台中处理,而不影响到用户的使用。前端
线程间通信编程
有一个很是重要的队列,就是主队列。在这个队列中处理多点触控及全部与UI相关操做等等。它很是特殊,缘由有两点。一是咱们绝对不想它阻塞,咱们不会将须要执行很长时间的任务放在主队列上执行。二是咱们将其用于全部与UI相关的同步,也就是线程间通信须要注意的地方。全部有可能会使屏幕UI发生变化的,都应放在主队列上执行。api
线程的定义:数组
每一个正在系统上运行的程序都是一个进程。每一个进程包含一到多个线程。进程也多是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它能够在程序里独立执行。也能够把它理解为代码运行的上下文。因此线程基本上是轻量级的进程,它负责在单个程序里执行多任务。一般由操做系统负责多个线程的调度和执行。
转自百度百科:多线程缓存
若是熟悉多线程编程技术这一块的朋友们,能够去看关于多线程安全的文章,是我写的另外一篇文章”iOS开发-多线程开发之线程安全篇“;安全
IOS支持的多线程技术:多线程
1、Thread:并发
2、Cocoa operations:
NSOperation类是一个抽象类,由于咱们必须使用它的两个子类。
————————————————————————————
3)NSOperationQueue(继承于NSObject)
3、Grand Central Dispatch (GCD):
2)重复执行线程及一次性执行:dispatch_apply & dispatch_once
3)操做(串行)队列:dispatch_queue_create
1、Thread
咱们可使用NSTherad或NSObject类去调用:
建立NSThread有两个办法
1.1)建立以后须要使用start方法,才会执行方法:
NSThread *threadAlloc = [[NSThread alloc] initWithTarget:self selector:@selector(threadAlloc) object:nil]; [threadAlloc start];
1.2)建立并立刻执行方法:
[NSThread detachNewThreadSelector:@selector(threadAlloc:) toTarget:self withObject:nil];
咱们也可使用NSObject类的方法直接调用方法
[self performSelectorInBackground:@selector(threadAlloc) withObject:nil];
取消线程的方法:
实际上并无真正提供取消线程的API。苹果提供了一个cancel的api,但它不能做用于取消线程,它只能改变线程的运行状态。咱们可使用它来进行条件判断。
- (void)threadCancel { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadCancelNow) object:nil]; [thread start]; } - (void)threadCancelNow { int a = 0; while (![[NSThread currentThread] isCancelled]) { NSLog(@"a - %d", a); a++; if (a == 5000) { NSLog(@"终止循环"); [[NSThread currentThread] cancel]; break; } } }
程序效果:循环输出5000次,线程就会被终止。
NSThread线程间通信-调用主线程修改UI:
只须要传递一个selector和它的参数,withObject参数能够为nil,waitUntilDone表明是否要等待调用它的这个线程执行以后再将它从主队列调出,并在主队列上运行,一般设为NO,不须要等待。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
NSThread相关属性及方法:
// 获取/设置线程的名字 @property (copy) NSString *name NS_AVAILABLE(10_5, 2_0); /** * 获取当前线程的线程对象 * * 经过这个属性能够查看当前线程是第几条线程,主线程为1。 * 能够看到当前线程的序号及名字,主线程的序号为1,依次叠加。 */ + (NSThread *)currentThread; // 线程休眠(秒) + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 线程休眠,指定具体什么时间休眠 + (void)sleepUntilDate:(NSDate *)date; // 退出线程 // 注意:这里会把线程对象销毁!销毁后就不能再次启动线程,不然程序会崩溃。 + (void)exit;
2、Cocoa operations
建立NSInvocationOperation线程,附带一个NSString参数:
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"abc"]; // 须要启动线程,默认是不启动的。 [operation start];
如在建立时定义了参数,那么接收的时候,能够对sender进行转换,如字符串、数组等:
- (void)invocationAction:(NSInvocationOperation *)sender { NSLog(@"sender - %@", sender); // 输出params NSString *str = (NSString *)sender; NSLog(@"str - %@e", str); // params }
附带一提,线程的普通建立通常为并发执行的,由于串行队列是须要显式建立的,如没看见此类代码,那么便是并发队列线程,所以,上述代码也就是并发线程。关于并发和串行队列(线程),我将会在下面详细说明,咱们继续往下看。
你也可使用NSOperationQueue来建立一个线程队列,用来添加子线程:
NSOperationQueue *invocationQueue = [[NSOperationQueue alloc] init]; // 线程A NSInvocationOperation *invocationQ1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ1"]; // 线程B NSInvocationOperation *invocationQ2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ2"]; // 往invocationQueue添加子线程 [invocationQueue addOperations:@[invocationQ1, invocationQ2] waitUntilFinished:YES];
必须使用addOperations:方法把线程添加至队列,否则线程不会执行,队列是并行执行。或者,你也可使用addOperation:方法添加单个线程。
建立NSBlockOperation
// 建立线程任务 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2]; NSLog(@"one - %@", [NSThread currentThread]); }];;// 执行线程任务 [blockOperation start];
注意:这会在当前的线程中执行,由于它是根据调用的线程所决定的。
比方说你在主线程中运行它,那么它就是在主线程中执行任务。若是你是在子线程中运行它,那么它就是在子线程中执行任务。
作个简单的实验,咱们新建一条子线程,而后在子线程里调用NSBlockOperation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2]; NSLog(@"one - %@", [NSThread currentThread]); // print: one - <NSThread: 0x7f8ac2e1d0b0>{number = 2, name = (null)} }];; [blockOperation start]; });
它将打印:one - <NSThread: 0x7f8ac2e1d0b0>{number = 2, name = (null)},所以这个理论是正确的
咱们也可使它并发执行,经过使用addExecutionBlock方法添加多个Block,这样就能使它在主线程和其它子线程中工做。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"one - %@", [NSThread currentThread]); }];; [blockOperation addExecutionBlock:^{ NSLog(@"two - %@", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"three - %@", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"four - %@", [NSThread currentThread]); }]; [blockOperation start];
它将打印:
two - <NSThread: 0x7fea8a70b000>{number = 3, name = (null)} one - <NSThread: 0x7fea8a558a40>{number = 4, name = (null)} four - <NSThread: 0x7fea8a406b90>{number = 1, name = main} three - <NSThread: 0x7fea8a436e40>{number = 2, name = (null)}
你们都看到,即便咱们经过使用addExecutionBlock方法使它并发执行任务,可是它也依旧会在主线程执行,所以咱们就须要使用NSOperationQueue了。
这里介绍一下NSOperation的依赖关系,依赖关系会影响线程的执行顺序:
// 建立操做队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 线程A NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1"); [NSThread sleepForTimeInterval:2]; }]; // 线程B NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op2"); }]; // 线程C NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op3"); [NSThread sleepForTimeInterval:2]; }]; // 线程B依赖线程C,也就是等线程C执行完以后才会执行线程B [op2 addDependency:op3]; // 线程C依赖线程A,同上,只不过依赖对象改为了线程A [op3 addDependency:op1]; // 为队列添加线程 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3];
当你没添加依赖时,队列是并行执行的。
注意:依赖关系能够多重依赖,但不要创建循环依赖。
Cocoa operations线程间通讯-调用主线程修改UI:
// 建立线程对象(并发) NSBlockOperation *blockOperation = [[NSBlockOperation alloc] init]; // 添加新的操做 [blockOperation addExecutionBlock:^{ NSLog(@"two - %@", [NSThread currentThread]); }]; // 添加新的操做 [blockOperation addExecutionBlock:^{ NSLog(@"three - %@", [NSThread currentThread]); // 在主线程修改UI NSOperationQueue *queue = [NSOperationQueue mainQueue]; [queue addOperationWithBlock:^{ [self editUINow]; }]; }]; [blockOperation start];
NSOperation方法及属性:
// 设置线程的最大并发数 @property NSInteger maxConcurrentOperationCount; // 线程完成后调用的Block @property (copy) void (^completionBlock)(void); // 取消线程 - (void)cancel;
只列举上面那些,其它的方法就不全列出来了。
注意:在NSOperationQueue类中,咱们可使用cancelAllOperations方法取消全部的线程。这里须要说明一下,不是执行cancelAllOperations方法时就会立刻取消,是等当前队列执行完,下面的队列不会再执行。
3、Grand Central Dispatch (GCD)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程 - %@", [NSThread currentThread]); });
GCD也能够建立同步的线程,只须要把async改为sync便可。
如下代码会执行4次:
dispatch_apply(4, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t index) { // index则为执行的次数 0开始递增 NSLog(@"one - %ld", index); });
index参数为执行的次数,从0开始递增。
其中须要注意的是,每次执行都会新开辟一条子线程,由于是异步的缘由,它们不会是顺序的。
[657:159159] one - 0, thread - <NSThread: 0x100110b50>{number = 1, name = main} [657:159191] one - 2, thread - <NSThread: 0x103800000>{number = 2, name = (null)} [657:159192] one - 3, thread - <NSThread: 0x100112b90>{number = 3, name = (null)} [657:159190] one - 1, thread - <NSThread: 0x100501180>{number = 4, name = (null)}
然而,GCD还有一次性执行的方法:
dispatch_once_t once; dispatch_once(&once, ^{ NSLog(@"once - %@", [NSThread currentThread]); // 主线程 });
它一般用于建立单例。
使用GCD也能建立串行队列,具体代码以下:
/** * GCD建立串行队列 * * @param "com.GarveyCalvin.queue" 队列字符串标识 * @param DISPATCH_QUEUE_CONCURRENT 可选的,能够是NULL * * @return dispatch_queue_t */ dispatch_queue_t queue = dispatch_queue_create("com.GarveyCalvin.queue", DISPATCH_QUEUE_CONCURRENT); // 线程A dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"sleep async - %@", [NSThread currentThread]); }); // 线程B dispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:3]; NSLog(@"sleep barrier2 - %@", [NSThread currentThread]); }); // 线程C dispatch_async(queue, ^{ NSLog(@"async"); });
运行效果:以上会先执行 线程A-》线程B-》线程C,它是一个串行队列。
dispatch_queue_create的第二个参数:
1)DISPATCH_QUEUE_SERIAL(串行)
2)DISPATCH_QUEUE_CONCURRENT(并发)
GCD的高级用法,等全部线程都完成工做后,再做通知。
// 建立群组 dispatch_group_t group = dispatch_group_create(); // 线程A dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group1"); [NSThread sleepForTimeInterval:2]; }); // 线程B dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group2"); }); // 待群组里的线程都完成以后调用的通知 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group success"); });
群组里的线程也是并行队列。线程A和线程B都执行完以后,会调用通知打印group success。
__block int time = 30; CGFloat reSecond = 1.0; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, reSecond * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(timer, ^{ time--; NSLog(@"%d", time); if (time == 0) { dispatch_source_cancel(timer); } }); dispatch_resume(timer);
代码效果:建立了一个计时器,计时器运行30秒,每过一秒会调用一次block,咱们能够在block里面写代码。由于dispatch_source_t默认是挂起状态,所以咱们使用时须要使用dispatch_resume方法先恢复,否则线程不会执行。
GCD线程间通讯-调用主线程修改UI:
有时候咱们请求后台做数据处理,数据处理是异步的,数据处理完成后须要更新UI,这时候咱们须要切换到主线程修改UI,例子以下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"异步数据处理 - %@", [NSThread currentThread]); [NSThread sleepForTimeInterval:2]; NSLog(@"数据处理完成"); // 调用主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"更新UI - %@", [NSThread currentThread]); [self editUINow]; }); });
由于是在主线程修改UI,因此咱们最好是使用同步的GCD方法dispatch_sync。但这还不够,咱们还须要使用dispatch_get_main_queue()方法来得到主线程,以后就是做UI的更新工做了。
GCD方法及属性:
// 获取主线程 dispatch_get_main_queue() // 建立队列:第一个参数是队列的名称,它会出如今调试程序等之中,是个内部名称。第二个参数表明它是串行队列仍是并并发队列,NULL表明串行队列。 dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 建立异步调度队列 void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); // 恢复队列 void dispatch_resume(dispatch_object_t object); // 暂停队列 void dispatch_suspend(dispatch_object_t object);
小结:本文主要介绍了IOS三种线程对比及其使用方法。须要特别注意的是,在修改任何有关于UI的东西,咱们必需要切换至主线程,在主线程里修改UI,避免没必要要的麻烦产生。苹果是推荐咱们使用GCD,由于GCD是这三种里面抽象级最高的,使用起来也简单,也是消耗资源最低的,而且它执行效率比其它两种都高。所以,可以使用GCD的地方,尽可能使用GCD。
使用block的另外一个好处是可让程序在后台较久地运行。在之前,当应用被按Home键退出后,应用仅有最多5秒的时间作一些保存或清理资源的工做。 可是若是使用GCD,你可让你的应用最多有10分钟的时间在后台长久运行。这个时间能够用来作各类事情,包括清理本地缓存、发送统计数据等工做。
AppDelegate.h @interface AppDelegate () @property (assign, nonatomic) UIBackgroundTaskIdentifier backGroundUpdate; @end AppDelegate.m - (void)applicationDidEnterBackground:(UIApplication *)application { [self beginBackGroundUpdate]; // 须要长久运行的代码 [self endBackGroundUpdate]; } - (void)beginBackGroundUpdate { self.backGroundUpdate = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackGroundUpdate]; }]; } - (void)endBackGroundUpdate { [[UIApplication sharedApplication] endBackgroundTask:self.backGroundUpdate]; self.backGroundUpdate = UIBackgroundTaskInvalid; }
建议你们在真机上测试,由于笔者在模拟器测试了24分钟还有效。
若是咱们想要某段代码延迟执行,那么可使用dispatch_after ,可是有一个缺点是,当提交代码后(代码执行后),咱们不能取消它,它将会运行。另外,咱们可使用 NSTimer 进行延时操做,值得一提,它是能够被取消的。
dispatch_time_t time_t = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_after(time_t, queue, ^{ NSLog(@"hahalo"); });
1、Thread:
优势:量级较轻。
缺点:须要本身管理线程的生命周期,线程同步。线程同步对数据的加锁会有必定的系统开销。
2、Cocoa operations:
优势:不须要关心线程管理,数据同步的事情,能够把精力放在本身须要执行的操做上。
3、Grand Central Dispatch (GCD):
优势:GCD基于C的API,很是底层,能够充分利用多核,可以轻松在多核系统上高效运行并发代码,也是苹果推荐使用的多线程技术。
本文参考:
全面掌握iOS多线程攻略 —— PS:这个攻略较多,可是有不少重复的内容。
博文做者:GarveyCalvin
博文出处:http://www.cnblogs.com/GarveyCalvin/
本文版权归做者和博客园共有,欢迎转载,但须保留此段声明,并给出原文连接,谢谢合做!