iOS开发-多线程编程技术(Thread、Cocoa operations、GCD)
来源:互联网 发布:南充58程序员 编辑:程序博客网 时间:2024/04/29 09:13
前言:在软件开发中,多线程编程技术被广泛应用,相信多线程任务对我们来说已经不再陌生了。有了多线程技术,我们可以同做多个事情,而不是一个一个任务地进行。比如:前端和后台作交互、大任务(需要耗费一定的时间和资源)等等。也就是说,我们可以使用线程把占据时间长的任务放到后台中处理,而不影响到用户的使用。
线程的定义:
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
转自百度百科:多线程
如果熟悉多线程编程技术这一块的朋友们,可以去看关于多线程安全的文章,是我写的另一篇文章”iOS开发-多线程开发之线程安全篇“;
IOS支持的多线程技术:
一、Thread:
1)显式创建线程:NSThreed
2)隐式创建线程:NSObject
二、Cocoa operations:
NSOperation类是一个抽象类,因为我们必须使用它的两个子类。
1)NSInvocationOperation
2)NSBlockOperation
————————————————————————————
3)NSOperationQueue(继承于NSObject)
三、Grand Central Dispatch (GCD):
1)GCD的创建
2)重复执行线程及一次性执行:dispatch_apply & dispatch_once
3)操作(串行)队列:dispatch_queue_create
4)GCD群组通知:dispatch_group_t
5)GCD实现计时器
6)后台运行
以上三种多线程技术的对比:
一、Thread:
优点:量级较轻。
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
二、Cocoa operations:
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
三、Grand Central Dispatch (GCD):
优点:GCD基于C语言的框架,可以充分利用多核,它能够轻松在多核系统上高效运行并发代码,是苹果推荐使用的多线程技术。
以上三种线程技术,抽象层次从低到高,抽象度越高使用则越简单,因此苹果推荐我们使用GCD。
Other、线程间通讯:
在多线程中,所有修改有关于UI界面的东西,必须切换到主线程中修改,不能直接在多线程中修改,不然很容易会出现异常或修改不成功。本文会对三个线程技术说明如何切换至主线程修改UI,具体方法请往下看。
一、Thread
我们可以使用NSTherad或NSObject类去调用:
1)显式创建线程:NSThread
创建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];
2)隐式创建线程:NSObject
我们也可以使用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:
- (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;
二、Cocoa operations
1)NSInvocationOperation
创建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];// 线程ANSInvocationOperation *invocationQ1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ1"];// 线程BNSInvocationOperation *invocationQ2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction:) object:@"invocationQ2"];// 往invocationQueue添加子线程[invocationQueue addOperations:@[invocationQ1, invocationQ2] waitUntilFinished:YES];
必须使用addOperations:方法把线程添加至队列,不然线程不会执行,队列是并行执行。或者,你也可以使用addOperation:方法添加单个线程。
2)NSBlockOperation
举个简单的使用例子介绍,都写了备注,就不再说明了:
// 创建线程任务NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2]; NSLog(@"one - %@", [NSThread currentThread]); }];;// 添加新的操作[blockOperation addExecutionBlock:^{ NSLog(@"two - %@", [NSThread currentThread]);}];// 添加新的操作[blockOperation addExecutionBlock:^{ NSLog(@"three - %@", [NSThread currentThread]);}];// 执行线程任务[blockOperation start];
例子效果:使用类方法blockOperationWithBlock:创建的块代码,是在主线程当中执行的,我们可以从打印出来的信息看到。其它使用addExecutionBlock:添加的子线程为并发线程。
另外,我发现了一个有趣的事情:
BOOL isMain = false; // 线程B是否为主线程NSBlockOperation *blockOperation;if (isMain) { // 线程是“主”线程 blockOperation = [[NSBlockOperation alloc] init];} else { // 线程是子线程 blockOperation = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2]; NSLog(@"MAIN主线程:%@", [NSThread currentThread]); }];}// 线程B[blockOperation addExecutionBlock:^{ NSLog(@"ismain - %d, %@", isMain, [NSThread currentThread]);}];[blockOperation start];
以上这段代码,是有趣的。当isMain=false,线程B是子线程,当isMain=true时,线程B是主线程。原因是NSBlockOperation默认是第一个添加的线程,它就是在主线程中工作。
3)NSOperationQueue
这里介绍一下NSOperation的依赖关系,依赖关系会影响线程的执行顺序:
// 创建操作队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 线程ANSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1"); [NSThread sleepForTimeInterval:2];}];// 线程BNSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op2");}];// 线程CNSBlockOperation *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方法时就会马上取消,是等当前队列执行完,下面的队列不会再执行。
三、Grand Central Dispatch (GCD)
1)GCD的创建:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程 - %@", [NSThread currentThread]);});
GCD也可以创建同步的线程,只需要把async改成sync即可。
2)重复执行线程:dispatch_apply
以下代码会执行4次:
dispatch_apply(4, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t index) { // index则为执行的次数 0开始递增 NSLog(@"one - %ld", index);});
index参数为执行的次数,从0开始递增。
然而,GCD还有一次性执行的方法,如下所示:
dispatch_once_t once;dispatch_once(&once, ^{ NSLog(@"once - %@", [NSThread currentThread]); // 主线程});
以上代码执行一次,如果你把它放到循环的地方,运行后将会崩溃。
3)操作队列:dispatch_queue_create
使用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);// 线程Adispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"sleep async - %@", [NSThread currentThread]);});// 线程Bdispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:3]; NSLog(@"sleep barrier2 - %@", [NSThread currentThread]);});// 线程Cdispatch_async(queue, ^{ NSLog(@"async");});
运行效果:以上会先执行 线程A-》线程B-》线程C,它是一个串行队列。
4)GCD群组通知:dispatch_group_t
GCD的高级用法,等所有线程都完成工作后,再作通知。
// 创建群组dispatch_group_t group = dispatch_group_create();// 线程Adispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"group1"); [NSThread sleepForTimeInterval:2];});// 线程Bdispatch_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。
5)GCD实现计时器
__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_sync(dispatch_get_main_queue(), ^{ NSLog(@"更新UI - %@", [NSThread currentThread]); [self editUINow]; });});
因为是在主线程修改UI,所以我们最好是使用同步的GCD方法dispatch_sync。但这还不够,我们还需要使用dispatch_get_main_queue()方法来获得主线程,之后就是作UI的更新工作了。
GCD方法及属性:
// 获取主线程dispatch_get_main_queue()// 创建队列:第一个参数是“字符串标识”,第二个参数是可选的,可以是NULLdispatch_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。
6)后台运行
使用block的另一个好处是可以让程序在后台较久地运行。在以前,当应用被按Home键退出后,应用仅有最多5秒的时间做一些保存或清理资源的工作。 但是如果使用GCD,你可以让你的应用最多有10分钟的时间在后台长久运行。这个时间可以用来做各种事情,包括清理本地缓存、发送统计数据等工作。
AppDelegate.h@interface AppDelegate ()@property (assign, nonatomic) UIBackgroundTaskIdentifier backGroundUpdate;@endAppDelegate.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分钟还有效。
本文参考:
iOS多线程开发
GCD的另一个用处是可以让程序在后台较长久的运行。
全面掌握iOS多线程攻略 —— PS:这个攻略较多,但是有很多重复的内容。
iOS多线程的初步研究(一)-- NSThread
博文作者:GarveyCalvin
博文出处:http://www.cnblogs.com/GarveyCalvin/
本文版权归作者和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作!
- iOS开发-多线程编程技术(Thread、Cocoa operations、GCD)
- [置顶]iOS开发-多线程编程技术(Thread、Cocoa operations、GCD)
- iOS(3)多线程编程技术(Thread Cocoa opreations GCD(Grand Central Dispatch ))
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS 多线程编程技术--NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
- win7+ubuntu 13.04双系统安装方法
- Android系统回顾(三):UI之GridLayout布局
- SVN服务器上的资源回复
- 1020. Tree Traversals
- Ubuntu 14.04+SUMO+Veins+OMNet++
- iOS开发-多线程编程技术(Thread、Cocoa operations、GCD)
- 使用数据库索引的优点与缺点
- IOS开发-第一个APP项目
- linux下修改文件的拥有者和用户组
- C# 冷门的语法和属性方法
- 亚像素类型的意义
- 巴菲特致股东的一封信:1991年
- [经典面试题][腾讯]选择原料工厂(最短距离问题)
- wince 下APP的背景