iOS 多线程的使用

来源:互联网 发布:冬枣 转基因 知乎 编辑:程序博客网 时间:2024/05/30 22:46

iOS中,多线程的使用有许多的方法,但是我们常用的有NSOperation,GCD,NSThread。那么我们今天就来看看这些多线程的使用。

NSOperation

优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。
NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

GCD

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。现在的iOS系统都升级到7了,所以不用担心该技术不能使用。

NSThread

优点:NSThread比其他两个轻量级;
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。

NSOperation

使用NSOperation的方式有两种,一种是用定义好的两个子类:NSInvocationOperation和NSBlockOperation;另一种是继承NSOperation。
如果你熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,只需继承重写NSOperation的一个方法main。相当与Java中Runnable的Run方法,然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。

NSInvocationOperation例子:

这里,我们实现一个下载图片的例子。新建一个Single View App,拖放一个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];   //用NSInvocationOperation创建了一个后台线程    NSOperationQueue *queue = [[NSOperationQueue alloc]init];       [queue addOperation:operation];   //将线程放到NSOperationQueue队列中,后台执行downloadImage方法。}   -(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 performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];   //下载完成后用performSelectorOnMainThread执行主线程updateUI方法。}   -(void)updateUI:(UIImage*) image{       self.imageView.image = image;   }@end

线上图片

继承NSOperation

在.m文件中实现main方法,main方法编写要执行的代码即可。

如何控制线程池中的线程数?

队列里可以加入很多个NSOperation,可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中线程可看作消费者,从队列中取走操作,并执行它。
通过下面的代码设置:[queue setMaxConcurrentOperationCount:5];
线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部操作。

GCD

介绍:Grand Central Dispatch简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6,IOS 4及以上也可用。
设计:
GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行。

dispatch queue分为下面三种:

Serial

又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。

Concurrent

又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。

Main dispatch queue

它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

dispatch queue的使用

常用的方法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 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();
虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release。

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);

dispatch_group_async是异步的方法,运行后可以看到打印结果:

2016-08-10 16:04:16.737 gcdTest[43328:11303] group1 2016-08-10 16:04:17.738 gcdTest[43328:12a1b] group2 2016-08-10 16:04:18.738 gcdTest[43328:13003] group3 2016-08-10 16:04:18.739 gcdTest[43328:f803] updateUi

每个一秒打印一个,当第三个任务执行后,upadteUi被打印。

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");   });

打印结果:

2016-08-10 16:20:33.967 gcdTest[45547:11203] dispatch_async1 2016-08-10 16:20:35.967 gcdTest[45547:11303] dispatch_async2 2016-08-10 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async 2016-08-10 16:20:40.970 gcdTest[45547:11303] dispatch_async3

请注意执行的时间,可以看到执行的顺序如上所述。

dispatch_apply

执行某个代码片段N次。

dispatch_apply(5, globalQ, ^(size_t index) {     // 执行5次 });

NSThread

NSThread 有两种直接创建方式:

- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

第一个是实例方法,第二个是类方法。

//1、[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];   //2、NSThread* myThread = [[NSThread alloc] initWithTarget:self                                           selector:@selector(doSomething:)                                           object:nil];   [myThread start];

参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值;
target :selector消息发送的对象;
argument:传输给target的唯一参数,也可以是nil。

第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息。

不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];

下载图片的例子:
新建singeView app
新建项目,并在xib文件上放置一个imageView控件。按住control键拖到viewController.h文件中创建imageView IBOutlet ViewController.m中实现:

#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 performSelectorOnMainThread:@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

线程间通讯:线程下载完图片后怎么通知主线程更新界面呢?

[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];

performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:

performSelector:onThread:withObject:waitUntilDone

运行结果如下图:

线程同步

我们演示一个经典的卖票的例子来讲NSThread的线程同步:

#import <UIKit/UIKit.h>   @class ViewController;   @interface AppDelegate : UIResponder <UIApplicationDelegate>   {       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 didFinishLaunchingWithOptions:(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];       }   }

如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。

上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了。

线程的顺序执行

他们都可以通过[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。

#import "AppDelegate.h"   #import "ViewController.h"   @implementation AppDelegate   - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(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];       }   }

wait是等待,我加了一个 线程3 去唤醒其他两个线程锁中的wait。

其他同步

我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。

- (void)doSomeThing:(id)anObj {     @synchronized(anObj)     {         // Everything between the braces is protected by the @synchronized directive.     } }

还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,可以自己看官方文档学习……

0 0
原创粉丝点击