iOS多線程

原文出處:容芳志的博客

簡(jiǎn)介

iOS有三種多線程編程的技術(shù),分別是:

(一)NSThread

(二)Cocoa NSOperation

(三)GCD(全稱:Grand Central Dispatch)

這三種編程方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡(jiǎn)單,也是Apple最推薦使用的。

三種方式的優(yōu)缺點(diǎn)介紹:

1)NSThread:

優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級(jí)

缺點(diǎn):需要自己管理線程的生命周期,線程同步。線程同步對(duì)數(shù)據(jù)的加鎖會(huì)有一定的系統(tǒng)開銷

NSThread實(shí)現(xiàn)的技術(shù)有下面三種:


NSThread實(shí)現(xiàn)的技術(shù)

一般使用cocoa thread 技術(shù)。

Cocoa NSOperation

優(yōu)點(diǎn):不需要關(guān)心線程管理,數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上。

Cocoa operation 相關(guān)的類是 NSOperation ,NSOperationQueue。

NSOperation是個(gè)抽象類,使用它必須用它的子類,可以實(shí)現(xiàn)它或者使用它定義好的兩個(gè)子類:NSInvocationOperation 和 NSBlockOperation。

創(chuàng)建NSOperation子類的對(duì)象,把對(duì)象添加到NSOperationQueue隊(duì)列里執(zhí)行。

GCD

Grand Central Dispatch (GCD)是Apple開發(fā)的一個(gè)多核編程的解決方法。在iOS4.0開始之后才能使用。GCD是一個(gè)替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術(shù)的很高效和強(qiáng)大的技術(shù)。現(xiàn)在的iOS系統(tǒng)都升級(jí)到9了,所以不用擔(dān)心該技術(shù)不能使用。

介紹完這三種多線程編程方式,本文將依次介紹這三種技術(shù)的使用。

(一)NSThread的使用

NSThread 有兩種直接創(chuàng)建方式:

-(id)initWithTarget:(id)targetselector:(SEL)selector object:(id)argument

+(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

第一個(gè)是實(shí)例方法,第二個(gè)是類方法

1、[NSThread detachNewThreadSelector:@selector(doSomething:)toTarget:self withObject:nil];

2、NSThread*myThread=[[NSThread alloc]initWithTarget:self?selector:@selector(doSomething:)?object:nil];

[myThread start];

參數(shù)的意義:

selector :線程執(zhí)行的方法,這個(gè)selector只能有一個(gè)參數(shù),而且不能有返回值。

target ?:selector消息發(fā)送的對(duì)象

argument:傳輸給target的唯一參數(shù),也可以是nil

第一種方式會(huì)直接創(chuàng)建線程并且開始運(yùn)行線程,第二種方式是先創(chuàng)建線程對(duì)象,然后再運(yùn)行線程操作,在運(yùn)行線程操作前可以設(shè)置線程的優(yōu)先級(jí)等線程信息

不顯式創(chuàng)建線程的方法:

用NSObject的類方法 ?performSelectorInBackground:withObject: 創(chuàng)建一個(gè)線程:

[Obj performSelectorInBackground:@selector(doSomething)withObject:nil];

下載圖片的例子:

新建singeView app

新建項(xiàng)目,并在xib文件上放置一個(gè)imageView控件。按住control鍵拖到viewController.h文件中創(chuàng)建imageView IBOutlet?ViewController.m中實(shí)現(xiàn):

#import "ViewController.h"

#define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"

@interfaceViewController()

@end

@implementationViewController

-(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

{

[superviewDidLoad];

//????[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的方法,除了可以更新主線程的數(shù)據(jù)外,還可以更新其他線程的比如:

performSelector:onThread:withObject:waitUntilDone:

運(yùn)行下載圖片:


運(yùn)行下載圖片

線程同步

我們演示一個(gè)經(jīng)典的賣票的例子來(lái)講NSThread的線程同步:

#import

@classViewController;

@interfaceAppDelegate:UIResponder

{

inttickets;

intcount;

NSThread*ticketsThreadone;

NSThread*ticketsThreadtwo;

NSCondition*ticketsCondition;

NSLock *theLock;

}

@property(strong,nonatomic)UIWindow *window;

@property(strong,nonatomic)ViewController *viewController;

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

tickets=100;

count=0;

theLock=[[NSLock alloc]init];

// 鎖對(duì)象

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

returnYES;

}

-(void)run{

while(TRUE){

// 上鎖

//????????[ticketsCondition lock];

[theLock lock];

if(tickets>=0){

[NSThread sleepForTimeInterval:0.09];

count=100-tickets;

NSLog(@"當(dāng)前票數(shù)是:%d,售出:%d,線程名:%@",tickets,count,[[NSThread currentThread]name]);

tickets--;

}else{

break;

}

[theLockunlock];

//????????[ticketsCondition unlock];

}

}

如果沒有線程同步的lock,賣票數(shù)可能是-1.加上lock之后線程同步保證了數(shù)據(jù)的正確性。

上面例子我使用了兩種鎖,一種NSCondition ,一種是:NSLock。 NSCondition我已經(jīng)注釋了。

線程的順序執(zhí)行

他們都可以通過(guò)[ticketsCondition signal]; 發(fā)送信號(hào)的方式,在一個(gè)線程喚醒另外一個(gè)線程的等待。

比如:

#import "AppDelegate.h"

#import "ViewController.h"

@implementationAppDelegate

-(BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

tickets=100;

count=0;

theLock=[[NSLock alloc]init];

// 鎖對(duì)象

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

returnYES;

}

-(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āng)前票數(shù)是:%d,售出:%d,線程名:%@",tickets,count,[[NSThread currentThread]name]);

tickets--;

}else{

break;

}

[theLock unlock];

[ticketsCondition unlock];

}

}

wait是等待,我加了一個(gè) 線程3 去喚醒其他兩個(gè)線程鎖中的wait。

其他同步

我們可以使用指令 @synchronized 來(lái)簡(jiǎn)化 NSLock的使用,這樣我們就不必顯示編寫創(chuàng)建NSLock,加鎖并解鎖相關(guān)代碼。

-(void)doSomeThing:(id)anObj

{

@synchronized(anObj)

{

// Everything between the braces is protected by the @synchronized directive.

}

?}

還有其他的一些鎖對(duì)象,比如:循環(huán)鎖NSRecursiveLock,條件鎖NSConditionLock,分布式鎖NSDistributedLock等等,可以自己看官方文檔學(xué)習(xí)

NSThread下載圖片的例子代碼:http://download.csdn.net/detail/totogo2010/4591149

(二)Cocoa NSOperation的使用

使用 NSOperation的方式有兩種,

一種是用定義好的兩個(gè)子類:NSInvocationOperation 和 NSBlockOperation。

另一種是繼承NSOperation

如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一樣,NSOperation也是設(shè)計(jì)用來(lái)擴(kuò)展的,只需繼承重寫NSOperation的一個(gè)方法main。相當(dāng)與java 中Runnalbe的Run方法。然后把NSOperation子類的對(duì)象放入NSOperationQueue隊(duì)列中,該隊(duì)列就會(huì)啟動(dòng)并開始處理它。

NSInvocationOperation例子:

這里同樣,我們實(shí)現(xiàn)一個(gè)下載圖片的例子。新建一個(gè)Single View app,拖放一個(gè)ImageView控件到xib界面。

實(shí)現(xiàn)代碼如下:

#import "ViewController.h"

#define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"

@interfaceViewController()

@end

@implementationViewController

-(void)viewDidLoad

{

[superviewDidLoad];

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 performSelectorOnMainThread:@selector(updateUI:)withObject:image waitUntilDone:YES];

}

-(void)updateUI:(UIImage*)image{

self.imageView.image=image;

}

代碼注釋:

1.viewDidLoad方法里可以看到我們用NSInvocationOperation建了一個(gè)后臺(tái)線程,并且放到

2.NSOperationQueue中。后臺(tái)線程執(zhí)行downloadImage方法。

3.downloadImage 方法處理下載圖片的邏輯。下載完成后用performSelectorOnMainThread執(zhí)行主線程updateUI方法。

updateUI 并把下載的圖片顯示到圖片控件中。

運(yùn)行可以看到下載圖片顯示在界面上。

下載圖片

第二種方式繼承NSOperation

在.m文件中實(shí)現(xiàn)main方法,main方法編寫要執(zhí)行的代碼即可。

如何控制線程池中的線程數(shù)?

隊(duì)列里可以加入很多個(gè)NSOperation, 可以把NSOperationQueue看作一個(gè)線程池,可往線程池中添加操作(NSOperation)到隊(duì)列中。線程池中的線程可看作消費(fèi)者,從隊(duì)列中取走操作,并執(zhí)行它。

通過(guò)下面的代碼設(shè)置:

[queue setMaxConcurrentOperationCount:5];

線程池中的線程數(shù),也就是并發(fā)操作數(shù)。默認(rèn)情況下是-1,-1表示沒有限制,這樣會(huì)同時(shí)運(yùn)行隊(duì)列中的全部的操作。

(三)GCD的介紹和使用

介紹:

Grand Central Dispatch 簡(jiǎn)稱(GCD)是蘋果公司開發(fā)的技術(shù),以優(yōu)化的應(yīng)用程序支持多核心處理器和其他的對(duì)稱多處理系統(tǒng)的系統(tǒng)。這建立在任務(wù)并行執(zhí)行的線程池模式的基礎(chǔ)上的。它首次發(fā)布在Mac OS X 10.6 ,iOS 4及以上也可用。

設(shè)計(jì):

GCD的工作原理是:讓程序平行排隊(duì)的特定任務(wù),根據(jù)可用的處理資源,安排他們?cè)谌魏慰捎玫奶幚砥骱诵纳蠄?zhí)行任務(wù)。

一個(gè)任務(wù)可以是一個(gè)函數(shù)(function)或者是一個(gè)block。 GCD的底層依然是用線程實(shí)現(xiàn),不過(guò)這樣可以讓程序員不用關(guān)注實(shí)現(xiàn)的細(xì)節(jié)。

GCD中的FIFO隊(duì)列稱為dispatch queue,它可以保證先進(jìn)來(lái)的任務(wù)先得到執(zhí)行。

dispatch queue分為下面三種:

Serial

又稱為private dispatch queues,同時(shí)只執(zhí)行一個(gè)任務(wù)。Serial queue通常用于同步訪問(wèn)特定的資源或數(shù)據(jù)。當(dāng)你創(chuàng)建多個(gè)Serial queue時(shí),雖然它們各自是同步執(zhí)行的,但Serial queue與Serial queue之間是并發(fā)執(zhí)行的。

Concurrent

又稱為global dispatch queue,可以并發(fā)地執(zhí)行多個(gè)任務(wù),但是執(zhí)行完成的順序是隨機(jī)的。

Main dispatch queue

它是全局可用的serial queue,它是在應(yīng)用程序主線程上執(zhí)行任務(wù)的。

我們看看dispatch queue如何使用?

1、常用的方法dispatch_async

為了避免界面在處理耗時(shí)的操作時(shí)卡死,比如讀取網(wǎng)絡(luò)數(shù)據(jù),IO,數(shù)據(jù)庫(kù)讀寫等,我們會(huì)在另外一個(gè)線程中處理這些操作,然后通知主線程更新界面。

用GCD實(shí)現(xiàn)這個(gè)流程的操作比前面介紹的NSThread ?NSOperation的方法都要簡(jiǎn)單。代碼框架結(jié)構(gòu)如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

// 耗時(shí)的操作

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;

});

}

});

運(yùn)行顯示:

下載圖片

是不是代碼比NSThread ?NSOperation簡(jiǎn)潔很多,而且GCD會(huì)自動(dòng)根據(jù)任務(wù)在多核處理器上分配資源,優(yōu)化程序。

系統(tǒng)給每一個(gè)應(yīng)用程序提供了三個(gè)concurrent dispatch queues。這三個(gè)并發(fā)調(diào)度隊(duì)列是全局的,它們只有優(yōu)先級(jí)的不同。因?yàn)槭侨值?,我們不需要去?chuàng)建。我們只需要通過(guò)使用函數(shù)dispath_get_global_queue去得到隊(duì)列,如下:

dispatch_queue_t globalQ=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

這里也用到了系統(tǒng)默認(rèn)就有一個(gè)串行隊(duì)列main_queue:1

dispatch_queue_t mainQ=dispatch_get_main_queue();

雖然dispatch queue是引用計(jì)數(shù)的對(duì)象,但是以上兩個(gè)都是全局的隊(duì)列,不用retain或release。

2、dispatch_group_async的使用

dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽一組任務(wù)是否完成,完成后得到通知執(zhí)行其他的操作。這個(gè)方法很有用,比如你執(zhí)行三個(gè)下載任務(wù),當(dāng)三個(gè)任務(wù)都下載完成后你才通知界面說(shuō)完成的了。下面是一段例子代碼:

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是異步的方法,運(yùn)行后可以看到打印結(jié)果:

2012-09-2516:04:16.737gcdTest[43328:11303]group1

2012-09-2516:04:17.738gcdTest[43328:12a1b]group2

2012-09-2516:04:18.738gcdTest[43328:13003]group3

2012-09-2516:04:18.739gcdTest[43328:f803]updateUi

每個(gè)一秒打印一個(gè),當(dāng)?shù)谌齻€(gè)任務(wù)執(zhí)行后,upadteUi被打印。

3、dispatch_barrier_async的使用

dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行

例子代碼如下:

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

});

打印結(jié)果:

2012-09-2516:20:33.967gcdTest[45547:11203]dispatch_async1

2012-09-2516:20:35.967gcdTest[45547:11303]dispatch_async2

2012-09-2516:20:35.967gcdTest[45547:11303]dispatch_barrier_async

2012-09-2516:20:40.970gcdTest[45547:11303]dispatch_async3

請(qǐng)注意執(zhí)行的時(shí)間,可以看到執(zhí)行的順序如上所述。

4、dispatch_apply

執(zhí)行某個(gè)代碼片段N次。

dispatch_apply(5,globalQ,^(size_t index){

// 執(zhí)行5次

});

本篇使用的到的例子代碼:http://download.csdn.net/detail/totogo2010/4596471

GCD還有很多其他用法,可以參考官方文檔http://en.wikipedia.org/wiki/Grand_Central_Dispatch

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 歡迎大家指出文章中需要改正或者需要補(bǔ)充的地方,我會(huì)及時(shí)更新,非常感謝。 一. 多線程基礎(chǔ) 1. 進(jìn)程 進(jìn)程是指在系...
    xx_cc閱讀 7,377評(píng)論 11 70
  • 1、簡(jiǎn)介:1.1 iOS有三種多線程編程的技術(shù),分別是:1.、NSThread2、Cocoa NSOperatio...
    LuckTime閱讀 1,452評(píng)論 0 1
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項(xiàng)。當(dāng)然也會(huì)給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 692評(píng)論 0 0
  • .一.進(jìn)程 進(jìn)程:是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空...
    IIronMan閱讀 4,604評(píng)論 1 33
  • 訪問(wèn)權(quán)限從高到低:open > public > interal > fileprivate > private ...
    rebeccaBull閱讀 183評(píng)論 0 0

友情鏈接更多精彩內(nèi)容