1、简介:
1.1iOS有三种多线程编程的技术,分别是:
1.、NSThread
2、CocoaNSOperation
3、GCD
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
这篇我们主要介绍和使用NSThread,后面会继续2、3 的讲解和使用。
1.2三种方式的有缺点介绍:
NSThread:
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread实现的技术有下面三种:
Technology | Description |
---|---|
Cocoa threads | Cocoa implements threads using the |
POSIX threads | POSIX threads provide a C-based interface for creating threads. Ifyou are not writing a Cocoa application, this is the best choicefor creating threads. The POSIX interface is relatively simple touse and offers ample flexibility for configuring your threads. Formore information, see |
Multiprocessing Services | Multiprocessing Services is a legacy C-based interface used byapplications transitioning from older versions of Mac OS. Thistechnology is available in OS X only and should be avoided for anynew development. Instead, you should usethe |
Cocoa operation
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation 相关的类是
GCD
Grand Central Dispatch(GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread,NSOperationQueue,NSInvocationOperation等技术的很高效和强大的技术。现在的iOS系统都升级到6了,所以不用担心该技术不能使用。
介绍完这三种多线程编程方式,我们这篇先介绍NSThread的使用。
2、NSThread的使用
2.1 NSThread有两种直接创建方式:
-(id)initWithTarget:(id)target
+(void)detachNewThreadSelector:(SEL)aSelector
第一个是实例方法,第二个是类方法
- 1、[NSThread
detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil]; - 2、NSThread*
myThread = [[NSThread alloc] initWithTarget:self -
selector:@selector(doSomething:) -
object:nil]; - [myThread
start];
2.2参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
target
argument:传输给target的唯一参数,也可以是nil
第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息
2.3PS:不显式创建线程的方法:
用NSObject的类方法
[Obj performSelectorInBackgro
2.4 下载图片的例子:
2.4.1 新建singeView app
新建项目,并在xib文件上放置一个imageView控件。按住control键拖到viewControll
er.h文件中创建imageView IBOutlet
ViewController.m中实现:
- //
- //
ViewController.m - //
NSThreadDemo - //
- //
Created by rongfzh on 12-9-23. - //
Copyright (c) 2012年 rongfzh. All rights reserved. - //
-
- #import
"ViewController.h" - #define
kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg" - @interface
ViewController () -
- @end
-
- @implementation
ViewController -
- -(void)downloadImage:(NSString
*) url{ -
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]]; -
UIImage *image = [[UIImage alloc]initWithData:data]; -
if(image == nil){ -
-
}else{ -
[self performSelectorOnMainThr ead:@selector(updateUI:) withObject:image waitUntilDone:YES]; -
} - }
-
- -(void)updateUI:(UIImage*)
image{ -
self.imageView.image = image; - }
-
-
- -
(void)viewDidLoad - {
-
[super viewDidLoad]; -
- //
[NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL]; -
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL]; -
[thread start]; - }
-
- -
(void)didReceiveMemoryWarning - {
-
[super didReceiveMemoryWarning]; -
// Dispose of any resources that can be recreated. - }
-
- @end
2.4.2线程间通讯
线程下载完图片后怎么通知主线程更新界面呢?
[self
用:performSelector:onThread:withObject:waitUntilDone:
2.3 线程同步
我们演示一个经典的卖票的例子来讲NSThread的线程同步:
.h
- #import
-
- @class
ViewController; -
- @interface
AppDelegate : UIResponder - {
-
int tickets; -
int count; -
NSThread* ticketsThreadone; -
NSThread* ticketsThreadtwo; -
NSCondition* ticketsCondition; -
NSLock *theLock; - }
- @property
(strong, nonatomic) UIWindow *window; -
- @property
(strong, nonatomic) ViewController *viewController; -
- @end
- -
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOp tions:(NSDictionary *)launchOptions - {
-
-
tickets = 100; -
count = 0; -
theLock = [[NSLock alloc] init]; -
// 锁对象 -
ticketsCondition = [[NSCondition alloc] init]; -
ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; -
[ticketsThreadone setName:@"Thread-1"]; -
[ticketsThreadone start]; -
-
-
ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; -
[ticketsThreadtwo setName:@"Thread-2"]; -
[ticketsThreadtwo start]; -
-
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; -
// Override point for customization after application launch. -
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; -
self.window.rootViewController = self.viewController; -
[self.window makeKeyAndVisible]; -
return YES; - }
-
- -
(void)run{ -
while (TRUE) { -
// 上锁 - //
[ticketsCondition lock]; -
[theLock lock]; -
if(tickets >= 0){ -
[NSThread sleepForTimeInterval:0.09]; -
count = 100 - tickets; -
NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]); -
tickets--; -
}else{ -
break; -
} -
[theLock unlock]; - //
[ticketsCondition unlock]; -
} - }
上面例子我使用了两种锁,一种NSCondition,一种是:NSLock。 NSCondition我已经注释了。
线程的顺序执行
他们都可以通过
比如:
- #import
"AppDelegate.h" -
- #import
"ViewController.h" -
- @implementation
AppDelegate -
- -
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOp tions:(NSDictionary *)launchOptions - {
-
-
tickets = 100; -
count = 0; -
theLock = [[NSLock alloc] init]; -
// 锁对象 -
ticketsCondition = [[NSCondition alloc] init]; -
ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; -
[ticketsThreadone setName:@"Thread-1"]; -
[ticketsThreadone start]; -
-
ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; -
[ticketsThreadtwo setName:@"Thread-2"]; -
[ticketsThreadtwo start]; -
-
NSThread *ticketsThreadthree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil]; -
[ticketsThreadthree setName:@"Thread-3"]; -
[ticketsThreadthree start]; -
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; -
// Override point for customization after application launch. -
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil]; -
self.window.rootViewController = self.viewController; -
[self.window makeKeyAndVisible]; -
return YES; - }
-
- -(void)run3{
-
while (YES) { -
[ticketsCondition lock]; -
[NSThread sleepForTimeInterval:3]; -
[ticketsCondition signal]; -
[ticketsCondition unlock]; -
} - }
-
- -
(void)run{ -
while (TRUE) { -
// 上锁 -
[ticketsCondition lock]; -
[ticketsCondition wait]; -
[theLock lock]; -
if(tickets >= 0){ -
[NSThread sleepForTimeInterval:0.09]; -
count = 100 - tickets; -
NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]); -
tickets--; -
}else{ -
break; -
} -
[theLock unlock]; -
[ticketsCondition unlock]; -
} - }
其他同步
我们可以使用指令 @synchronized 来简化NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。
- (void)doSomeThing:(id)anObj
{
}
还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,可以自己看官方文档学习
使用 NSOperation的方式有两种,
一种是用定义好的两个子类:
NSInvocationOperation 和NSBlockOperation。
另一种是继承NSOperation
如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,只需继承重写NSOperation的一个方法main。相当与java中Runnalbe的Run方法。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。
NSInvocationOperation例子:
和前面一篇博文一样,我们实现一个下载图片的例子。新建一个Single Viewapp,拖放一个ImageView控件到xib界面。
实现代码如下:
- #import
"ViewController.h" - #define
kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg" -
- @interface
ViewController () -
- @end
-
- @implementation
ViewController -
- -
(void)viewDidLoad - {
-
[super viewDidLoad]; -
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self -
selector:@selector(downloadImage:) -
object:kURL]; -
-
NSOperationQueue *queue = [[NSOperationQueue alloc]init]; -
[queue addOperation:operation]; -
// Do any additional setup after loading the view, typically from a nib. - }
-
- -(void)downloadImage:(NSString
*)url{ -
NSLog(@"url:%@", url); -
NSURL *nsUrl = [NSURL URLWithString:url]; -
NSData *data = [[NSData alloc]initWithContentsOfURL:nsUrl]; -
UIImage * image = [[UIImage alloc]initWithData:data]; -
[self performSelectorOnMainThr ead:@selector(updateUI:) withObject:image waitUntilDone:YES]; - }
- -(void)updateUI:(UIImage*)
image{ -
self.imageView.image = image; - }
- viewDidLoad方法里可以看到我们用NSInvocationOperation建了一个后台线程,并且放到NSOperationQueue中。后台线程执行downloadImage方法。
- downloadImage方法处理下载图片的逻辑。下载完成后用performSelectorOnMainThr
ead执行主线程updateUI方法。 - updateUI 并把下载的图片显示到图片控件中。
-
第二种方式继承NSOperation
在.m文件中实现main方法,main方法编写要执行的代码即可。
如何控制线程池中的线程数?
队列里可以加入很多个NSOperation,
可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。 通过下面的代码设置:
[queue setMaxConcurrentOperationCount:5];
线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。
gcd
介绍:
Grand Central Dispatch简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在MacOS X 10.6 ,iOS 4及以上也可用。
设计:
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。
GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。 GCD中的FIFO队列称为dispatchqueue,它可以保证先进来的任务先得到执行
dispatch queue分为下面三种:
Serial
又称为private dispatchqueues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serialqueue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
Concurrent
又称为global dispatchqueue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
Main dispatchqueue
它是全局可用的serialqueue,它是在应用程序主线程上执行任务的。
我们看看dispatch queue如何使用
1、常用的方法dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。
用GCD实现这个流程的操作比前面介绍的NSThread
NSOperation的方法都要简单。代码框架结构如下: - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{ -
// 耗时的操作 -
dispatch_async(dispatch_get_main_queue(), ^{ -
// 更新界面 -
}); - });
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{ -
NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"]; -
NSData * data = [[NSData alloc]initWithContentsOfURL:url]; -
UIImage *image = [[UIImage alloc]initWithData:data]; -
if (data != nil) { -
dispatch_async(dispatch_get_main_queue(), ^{ -
self.imageView.image = image; -
}); -
} - });
- 是不是代码比NSThread
NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。 系统给每一个应用程序提供了三个concurrent dispatchqueues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:
- dispatch_queue_t
globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
这里也用到了系统默认就有一个串行队列main_queue
虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release。
2、dispatch_group_async的使用
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:
- dispatch_queue_t
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_group_t
group = dispatch_group_create(); - dispatch_group_async(group,
queue, ^{ -
[NSThread sleepForTimeInterval:1]; -
NSLog(@"group1"); - });
- dispatch_group_async(group,
queue, ^{ -
[NSThread sleepForTimeInterval:2]; -
NSLog(@"group2"); - });
- dispatch_group_async(group,
queue, ^{ -
[NSThread sleepForTimeInterval:3]; -
NSLog(@"group3"); - });
- dispatch_group_notify(group,
dispatch_get_main_queue(), ^{ -
NSLog(@"updateUi"); - });
- dispatch_release(group);
2012-09-25 16:04:16.737 gcdTest[43328:11303] group1
2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2
2012-09-25 16:04:18.738 gcdTest[43328:13003] group3
2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi
每个一秒打印一个,当第三个任务执行后,upadteUi被打印。
3、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
例子代码如下:
- dispatch_queue_t
queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT); - dispatch_async(queue,
^{ -
[NSThread sleepForTimeInterval:2]; -
NSLog(@"dispatch_async1"); - });
- dispatch_async(queue,
^{ -
[NSThread sleepForTimeInterval:4]; -
NSLog(@"dispatch_async2"); - });
- dispatch_barrier_async(queue,
^{ -
NSLog(@"dispatch_barrier_async"); -
[NSThread sleepForTimeInterval:4]; -
- });
- dispatch_async(queue,
^{ -
[NSThread sleepForTimeInterval:1]; -
NSLog(@"dispatch_async3"); - });
打印结果:
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次
});本篇使用的到的例子代码:http://download.csdn.net/detail/totogo2010/4596471
GCD还有很多其他用法,可以参考官方文档
参考的文档还有:http://en.wikipedia.org/wiki/Grand_Central_Dispatch
前两篇多线程博文:iOS多线程编程之NSThread的使用
- dispatch_queue_t
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,