1介评、簡介:
1.1 iOS有三種多線程編程的技術(shù)库北,分別是:
1.、NSThread
2、Cocoa NSOperation (iOS多線程編程之NSOperation和NSOperationQueue的使用)
3贤惯、GCD 全稱:Grand Central Dispatch( iOS多線程編程之Grand Central Dispatch(GCD)介紹和使用)
這三種編程方式從上到下洼专,抽象度層次是從低到高的,抽象度越高的使用越簡單孵构,也是Apple最推薦使用的屁商。
這篇我們主要介紹和使用NSThread,后面會繼續(xù)2颈墅、3 的講解和使用蜡镶。
1.2 三種方式的有缺點(diǎn)介紹:
NSThread:
優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級
缺點(diǎn):需要自己管理線程的生命周期,線程同步恤筛。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷
NSThread實(shí)現(xiàn)的技術(shù)有下面三種:
Technology
Description
Cocoa threads
Cocoa implements threads using the NSThread class. Cocoa also provides methods on NSObject for spawning new threads and executing code on already-running threads. For more information, see “Using NSThread” and “Using NSObject to Spawn a Thread.”
POSIX threads
POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads”
Multiprocessing Services
Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in OS X only and should be avoided for any new development. Instead, you should use the NSThread class or POSIX threads. If you need more information on this technology, see Multiprocessing Services Programming Guide.
一般使用cocoa thread 技術(shù)官还。
Cocoa operation
優(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子類的對象劣摇,把對象添加到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)都升級到6了暇韧,所以不用擔(dān)心該技術(shù)不能使用勾习。
介紹完這三種多線程編程方式,我們這篇先介紹NSThread的使用懈玻。
2语卤、NSThread的使用
2.1 NSThread 有兩種直接創(chuàng)建方式:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
- (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
第一個(gè)是實(shí)例方法,第二個(gè)是類方法
[cpp] view plaincopy
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參數(shù)的意義:
selector :線程執(zhí)行的方法钮孵,這個(gè)selector只能有一個(gè)參數(shù)骂倘,而且不能有返回值。
target :selector消息發(fā)送的對象
argument:傳輸給target的唯一參數(shù)巴席,也可以是nil
第一種方式會直接創(chuàng)建線程并且開始運(yùn)行線程,第二種方式是先創(chuàng)建線程對象,然后再運(yùn)行線程操作堰塌,在運(yùn)行線程操作前可以設(shè)置線程的優(yōu)先級等線程信息
2.3 PS:不顯式創(chuàng)建線程的方法:
用NSObject的類方法 performSelectorInBackground:withObject: 創(chuàng)建一個(gè)線程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
2.4 下載圖片的例子:
2.4.1 新建singeView app
新建項(xiàng)目分衫,并在xib文件上放置一個(gè)imageView控件。按住control鍵拖到viewControll
er.h文件中創(chuàng)建imageView IBOutlet
ViewController.m中實(shí)現(xiàn):
[cpp] view plaincopy
//
// 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 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
2.4.2線程間通訊
線程下載完圖片后怎么通知主線程更新界面呢牵现?
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
performSelectorOnMainThread是NSObject的方法邀桑,除了可以更新主線程的數(shù)據(jù)外,還可以更新其他線程的比如:
用:performSelector:onThread:withObject:waitUntilDone:
運(yùn)行下載圖片:
圖片下載下來了贼急。
2.3 線程同步
我們演示一個(gè)經(jīng)典的賣票的例子來講NSThread的線程同步:
.h
[cpp] view plaincopy
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
[cpp] view plaincopy
-
(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āng)前票數(shù)是:%d,售出:%d,線程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
// [ticketsCondition unlock];
}
}
如果沒有線程同步的lock太抓,賣票數(shù)可能是-1.加上lock之后線程同步保證了數(shù)據(jù)的正確性照弥。
上面例子我使用了兩種鎖,一種NSCondition 这揣,一種是:NSLock。 NSCondition我已經(jīng)注釋了机打。
線程的順序執(zhí)行
他們都可以通過
[ticketsCondition signal]; 發(fā)送信號的方式,在一個(gè)線程喚醒另外一個(gè)線程的等待残邀。
比如:
[cpp] view plaincopy
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āng)前票數(shù)是:%d,售出:%d,線程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
[ticketsCondition unlock];
}
}
wait是等待芥挣,我加了一個(gè) 線程3 去喚醒其他兩個(gè)線程鎖中的wait
其他同步
我們可以使用指令 @synchronized 來簡化 NSLock的使用耻台,這樣我們就不必顯示編寫創(chuàng)建NSLock,加鎖并解鎖相關(guān)代碼。
- (void)doSomeThing:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
還有其他的一些鎖對象盆耽,比如:循環(huán)鎖NSRecursiveLock扼菠,條件鎖NSConditionLock循榆,分布式鎖NSDistributedLock等等,可以自己看官方文檔學(xué)習(xí)
NSThread下載圖片的例子代碼:http://download.csdn.net/detail/totogo2010/4591149
著作權(quán)聲明:本文由http://blog.csdn.net/totogo2010/原創(chuàng),歡迎轉(zhuǎn)載分享秧饮。請尊重作者勞動框杜,轉(zhuǎn)載時(shí)保留該聲明和作者博客鏈接,謝謝!
《iOS多線程編程之NSThread的使用》
介紹三種多線程編程和NSThread的使用咪辱,這篇介紹NSOperation的使用。
使用 NSOperation的方式有兩種油狂,
一種是用定義好的兩個(gè)子類:
NSInvocationOperation 和 NSBlockOperation。
另一種是繼承NSOperation
如果你也熟悉Java专筷,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一樣吮旅,NSOperation也是設(shè)計(jì)用來擴(kuò)展的,只需繼承重寫NSOperation的一個(gè)方法main庇勃。相當(dāng)與java 中Runnalbe的Run方法槽驶。然后把NSOperation子類的對象放入NSOperationQueue隊(duì)列中,該隊(duì)列就會啟動并開始處理它掂铐。
NSInvocationOperation例子:
和前面一篇博文一樣,我們實(shí)現(xiàn)一個(gè)下載圖片的例子爆班。新建一個(gè)Single View app,拖放一個(gè)ImageView控件到xib界面蛋济。
實(shí)現(xiàn)代碼如下:
[cpp] view plaincopy
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 performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
}
-(void)updateUI:(UIImage) image{
self.imageView.image = image;
}
viewDidLoad方法里可以看到我們用NSInvocationOperation建了一個(gè)后臺線程碗旅,并且放到NSOperationQueue中。后臺線程執(zhí)行downloadImage方法祟辟。
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í)行它潮剪。
通過下面的代碼設(shè)置:
[queue setMaxConcurrentOperationCount:5];
線程池中的線程數(shù),也就是并發(fā)操作數(shù)总棵。默認(rèn)情況下是-1,-1表示沒有限制情龄,這樣會同時(shí)運(yùn)行隊(duì)列中的全部的操作捍壤。
介紹:
Grand Central Dispatch 簡稱(GCD)是蘋果公司開發(fā)的技術(shù),以優(yōu)化的應(yīng)用程序支持多核心處理器和其他的對稱多處理系統(tǒng)的系統(tǒng)鹃觉。這建立在任務(wù)并行執(zhí)行的線程池模式的基礎(chǔ)上的。它首次發(fā)布在Mac OS X 10.6 盗扇,iOS 4及以上也可用祷肯。
設(shè)計(jì):
GCD的工作原理是:讓程序平行排隊(duì)的特定任務(wù),根據(jù)可用的處理資源佑笋,安排他們在任何可用的處理器核心上執(zhí)行任務(wù)。
一個(gè)任務(wù)可以是一個(gè)函數(shù)(function)或者是一個(gè)block猎荠。 GCD的底層依然是用線程實(shí)現(xiàn)蜀备,不過這樣可以讓程序員不用關(guān)注實(shí)現(xiàn)的細(xì)節(jié)。
GCD中的FIFO隊(duì)列稱為dispatch queue碾阁,它可以保證先進(jìn)來的任務(wù)先得到執(zhí)行
dispatch queue分為下面三種:
Serial
又稱為private dispatch queues,同時(shí)只執(zhí)行一個(gè)任務(wù)瓷蛙。Serial queue通常用于同步訪問特定的資源或數(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ù)庫讀寫等蝶溶,我們會在另外一個(gè)線程中處理這些操作,然后通知主線程更新界面宣渗。
用GCD實(shí)現(xiàn)這個(gè)流程的操作比前面介紹的NSThread NSOperation的方法都要簡單梨州。代碼框架結(jié)構(gòu)如下:
[cpp] view plaincopy
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗時(shí)的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
});
});
如果這樣還不清晰的話摊唇,那我們還是用上兩篇博客中的下載圖片為例子,代碼如下:
[cpp] view plaincopy
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簡潔很多,而且GCD會自動根據(jù)任務(wù)在多核處理器上分配資源有序,優(yōu)化程序。
系統(tǒng)給每一個(gè)應(yīng)用程序提供了三個(gè)concurrent dispatch queues旭寿。這三個(gè)并發(fā)調(diào)度隊(duì)列是全局的,它們只有優(yōu)先級的不同肩祥。因?yàn)槭侨值模覀儾恍枰?chuàng)建混狠。我們只需要通過使用函數(shù)dispath_get_global_queue去得到隊(duì)列疾层,如下:
[cpp] view plaincopy
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
這里也用到了系統(tǒng)默認(rèn)就有一個(gè)串行隊(duì)列main_queue
[cpp] view plaincopy
dispatch_queue_t mainQ = dispatch_get_main_queue();
雖然dispatch queue是引用計(jì)數(shù)的對象,但是以上兩個(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ù)都下載完成后你才通知界面說完成的了蝇恶。下面是一段例子代碼:
[cpp] view plaincopy
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-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
每個(gè)一秒打印一個(gè)撮弧,當(dāng)?shù)谌齻€(gè)任務(wù)執(zhí)行后姚糊,upadteUi被打印授舟。
3、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行肠槽,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行
例子代碼如下:
[cpp] view plaincopy
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-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
請注意執(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
前兩篇多線程博文:iOS多線程編程之NSThread的使用
iOS多線程編程之NSOperation和NSOperationQueue的使用
著作權(quán)聲明:本文由http://blog.csdn.net/totogo2010/原創(chuàng),歡迎轉(zhuǎn)載分享拟杉。請尊重作者勞動伤靠,轉(zhuǎn)載時(shí)保留該聲明和作者博客鏈接,謝謝!