iOS有三种多线程编程的技术,分别是:
1.、NSThread
2、CocoaNSOperation
3、GCD 全称:Grand Central Dispatch
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
三种方式的有缺点介绍:
NSThread:
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread实现的技术有下面三种:
Technology |
Cocoa threads |
POSIX threads |
Multiprocessing Services |
一般使用cocoa thread 技术。
Cocoa operation
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。
GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread,NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。现在的iOS系统都升级到6了,所以不用担心该技术不能使用。
详细介绍:
1、NSThread的使用
1.1 NSThread 有两种直接创建方式:
1. 1、[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
2. 2、NSThread* myThread = [[NSThread alloc] initWithTarget:self
3. selector:@selector(doSomething:)
4. object:nil];
5. [myThread start];
1.2 不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething)withObject:nil];
1.3 下载图片的demo:
1. #import "ViewController.h"
2. #define kURL @"http://XXX.jpg"
3. @interface ViewController ()
4.
5. @end
6.
7. @implementation ViewController
8.
9. -(void)downloadImage:(NSString *) url{
10. NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
11. UIImage *image = [[UIImage alloc]initWithData:data];
12. if(image == nil){
13.
14. }else{
15. [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
16. }
17. }
18.
19. -(void)updateUI:(UIImage*) image{
20. self.imageView.image = image;
21. }
22.
23.
24. - (void)viewDidLoad
25. {
26. [super viewDidLoad];
27.
28. // [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];
29. NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL];
30. [thread start];
31. }
32.
33. - (void)didReceiveMemoryWarning
34. {
35. [super didReceiveMemoryWarning];
36. // Dispose of any resources that can be recreated.
37. }
38.
39. @end
1.4 线程间通讯
[selfperformSelectorOnMainThread:@selector(updateUI:) withObject:imagewaitUntilDone:YES];
performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:
用:performSelector:onThread:withObject:waitUntilDone:
1.5 线程同步
我们演示一个经典的卖票的例子来讲NSThread的线程同步:
.h
[cpp]viewplaincopy
1. #import <UIKit/UIKit.h>
2.
3. @class ViewController;
4.
5. @interface AppDelegate : UIResponder <UIApplicationDelegate>
6. {
7. int tickets;
8. int count;
9. NSThread* ticketsThreadone;
10. NSThread* ticketsThreadtwo;
11. NSCondition* ticketsCondition;
12. NSLock *theLock;
13. }
14. @property (strong, nonatomic) UIWindow *window;
15.
16. @property (strong, nonatomic) ViewController *viewController;
17.
18. @end
[cpp]viewplaincopy
1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
2. {
3.
4. tickets = 100;
5. count = 0;
6. theLock = [[NSLock alloc] init];
7. // 锁对象
8. ticketsCondition = [[NSCondition alloc] init];
9. ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
10. [ticketsThreadone setName:@"Thread-1"];
11. [ticketsThreadone start];
12.
13.
14. ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
15. [ticketsThreadtwo setName:@"Thread-2"];
16. [ticketsThreadtwo start];
17.
18. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
19. // Override point for customization after application launch.
20. self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
21. self.window.rootViewController = self.viewController;
22. [self.window makeKeyAndVisible];
23. return YES;
24. }
25.
26. - (void)run{
27. while (TRUE) {
28. // 上锁
29. // [ticketsCondition lock];
30. [theLock lock];
31. if(tickets >= 0){
32. [NSThread sleepForTimeInterval:0.09];
33. count = 100 - tickets;
34. NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);
35. tickets--;
36. }else{
37. break;
38. }
39. [theLock unlock];
40. // [ticketsCondition unlock];
41. }
42. }
如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。
上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了。
1.6线程的顺序执行
他们都可以通过
[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。
比如:
[cpp]viewplaincopy
1. #import "AppDelegate.h"
2.
3. #import "ViewController.h"
4.
5. @implementation AppDelegate
6.
7. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8. {
9.
10. tickets = 100;
11. count = 0;
12. theLock = [[NSLock alloc] init];
13. // 锁对象
14. ticketsCondition = [[NSCondition alloc] init];
15. ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
16. [ticketsThreadone setName:@"Thread-1"];
17. [ticketsThreadone start];
18.
19. ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
20. [ticketsThreadtwo setName:@"Thread-2"];
21. [ticketsThreadtwo start];
22.
23. NSThread *ticketsThreadthree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil];
24. [ticketsThreadthree setName:@"Thread-3"];
25. [ticketsThreadthree start];
26. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
27. // Override point for customization after application launch.
28. self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
29. self.window.rootViewController = self.viewController;
30. [self.window makeKeyAndVisible];
31. return YES;
32. }
33.
34. -(void)run3{
35. while (YES) {
36. [ticketsCondition lock];
37. [NSThread sleepForTimeInterval:3];
38. [ticketsCondition signal];
39. [ticketsCondition unlock];
40. }
41. }
42.
43. - (void)run{
44. while (TRUE) {
45. // 上锁
46. [ticketsCondition lock];
47. [ticketsCondition wait];
48. [theLock lock];
49. if(tickets >= 0){
50. [NSThread sleepForTimeInterval:0.09];
51. count = 100 - tickets;
52. NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);
53. tickets--;
54. }else{
55. break;
56. }
57. [theLock unlock];
58. [ticketsCondition unlock];
59. }
60. }
wait是等待,我加了一个 线程3 去唤醒其他两个线程锁中的wait
1.7 其他同步
我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。
-(void)doSomeThing:(id)anObj
{ @synchronized(anObj) {
}
} 还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,
2.NSOperation的方式有两种,
2.1一种是用定义好的两个子类:
NSInvocationOperation 和NSBlockOperation。
a.
NSInvocationOperation *operation = [[NSInvocationOperation
alloc]initWithTarget:self
selector:@selector(downloadImage:)
object:kURL];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
b.
NSBlockOperation *operation = [NSBlockOperationblockOperationWithBlock:^{
[self downImage:kURL];
}];
[queue addOperation:operation];
第二种方式继承NSOperation
在.m文件中实现main方法,main方法编写要执行的代码即可。
2.2如何控制线程池中的线程数?
队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。
通过设置: [queuesetMaxConcurrentOperationCount:5];
线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。
3、GCD
Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
GCD中的FIFO队列称为dispatchqueue,它可以保证先进来的任务先得到执行 dispatch queue分为下面三种:
Serial
又称为privatedispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
Concurrent
又称为globaldispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
Main dispatch queue
它是全局可用的serialqueue,它是在应用程序主线程上执行任务的。
dispatchqueue如何使用
1、常用的方法dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。
用GCD实现这个流程的操作比前面介绍的NSThread NSOperation的方法都要简单。代码框架结构如下:
[cpp]view plaincopy
6. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
7. // 耗时的操作
8. dispatch_async(dispatch_get_main_queue(), ^{
9. // 更新界面
10. });
11. });
代码如下:
40. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
41. NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
42. NSData * data = [[NSData alloc]initWithContentsOfURL:url];
43. UIImage *image = [[UIImage alloc]initWithData:data];
44. if (data != nil) {
45. dispatch_async(dispatch_get_main_queue(), ^{
46. self.imageView.image = image;
47. });
48. }
49. });
系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
这里也用到了系统默认就有一个串行队列main_queue
dispatch_queue_t mainQ = dispatch_get_main_queue();
虽然dispatchqueue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release。
2、dispatch_group_async的使用
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:
61. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
62. dispatch_group_t group = dispatch_group_create();
63. dispatch_group_async(group, queue, ^{
64. [NSThread sleepForTimeInterval:1];
65. NSLog(@"group1");
66. });
67. dispatch_group_async(group, queue, ^{
68. [NSThread sleepForTimeInterval:2];
69. NSLog(@"group2");
70. });
71. dispatch_group_async(group, queue, ^{
72. [NSThread sleepForTimeInterval:3];
73. NSLog(@"group3");
74. });
75. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
76. NSLog(@"updateUi");
77. });
78. dispatch_release(group);
dispatch_group_async是异步的方法,运行后可以看到打印结果:
2012-09-25 16:04:16.737 gcdTest[43328:11303]group1 2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2 2012-09-2516:04:18.738 gcdTest[43328:13003] group3 2012-09-25 16:04:18.739gcdTest[43328:f803] updateUi
每个一秒打印一个,当第三个任务执行后,upadteUi被打印。
3、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
例子代码如下:
1. dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
2. dispatch_async(queue, ^{
3. [NSThread sleepForTimeInterval:2];
4. NSLog(@"dispatch_async1");
5. });
6. dispatch_async(queue, ^{
7. [NSThread sleepForTimeInterval:4];
8. NSLog(@"dispatch_async2");
9. });
10. dispatch_barrier_async(queue, ^{
11. NSLog(@"dispatch_barrier_async");
12. [NSThread sleepForTimeInterval:4];
13.
14. });
15. dispatch_async(queue, ^{
16. [NSThread sleepForTimeInterval:1];
17. NSLog(@"dispatch_async3");
18. });
打印结果:
2012-09-25 16:20:33.967 gcdTest[45547:11203]dispatch_async1
2012-09-25 16:20:35.967 gcdTest[45547:11303]dispatch_async2
2012-09-25 16:20:35.967 gcdTest[45547:11303]dispatch_barrier_async
2012-09-25 16:20:40.970 gcdTest[45547:11303]dispatch_async3
请注意执行的时间,可以看到执行的顺序如上所述。
4、dispatch_apply
执行某个代码片段N次。 dispatch_apply(5,globalQ, ^(size_t index) { // 执行5次 });
5. // 一次性执行:
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
// 延迟2秒执行:
doubledelayInSeconds=2.0;
dispatch_time_tpopTime=dispatch_time(DISPATCH_TIME_NOW, delayInSeconds*NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queueafter delay
});
dispatch_queue_t 也可以自己定义,如要要自定义queue,可以用dispatch_queue_create方法,示例如下:
| dispatch_queue_turls_queue=dispatch_queue_create(“xx", NULL); dispatch_async(urls_queue, ^{ // your code }); dispatch_release(urls_queue);
后台运行 GCD的另一个用处是可以让程序在后台较长久的运行。 在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟 的时候做一些保存或清理资源的工作。但是在使用GCD后, app最多有10分钟的时间在后台长久运行。 这个时间可以用来做清理本地缓存,发送统计数据等工作。 让程序在后台长久运行的示例代码如下:
|
本文由http://blog.csdn.net/totogo2010/原创,由本人简化修改后转载,在这里感谢原作者。