1、簡(jiǎn)介:
1.1 iOS有三種多線(xiàn)程編程的技術(shù),分別是:
1.、NSThread
2、Cocoa NSOperation (iOS多線(xiàn)程編程之NSOperation和NSOperationQueue的使用)
3、GCD 全稱(chēng):Grand Central Dispatch( iOS多線(xiàn)程編程之Grand Central Dispatch(GCD)介紹和使用)
這三種編程方式從上到下,抽象度層次是從低到高的,抽象度越高的使用越簡(jiǎn)單,也是Apple最推薦使用的。
這篇我們主要介紹和使用NSThread,后面會(huì)繼續(xù)2、3 的講解和使用。
1.2 三種方式的有缺點(diǎn)介紹:
NSThread:
優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級(jí)
缺點(diǎn):需要自己管理線(xiàn)程的生命周期,線(xiàn)程同步。線(xiàn)程同步對(duì)數(shù)據(jù)的加鎖會(huì)有一定的系統(tǒng)開(kāi)銷(xiāo)
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)心線(xiàn)程管理,數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上。
Cocoa operation 相關(guān)的類(lèi)是 NSOperation ,NSOperationQueue。NSOperation是個(gè)抽象類(lèi),使用它必須用它的子類(lèi),可以實(shí)現(xiàn)它或者使用它定義好的兩個(gè)子類(lèi):NSInvocationOperation 和 NSBlockOperation。創(chuàng)建NSOperation子類(lèi)的對(duì)象,把對(duì)象添加到NSOperationQueue隊(duì)列里執(zhí)行。
GCD
Grand Central Dispatch (GCD)是Apple開(kāi)發(fā)的一個(gè)多核編程的解決方法。在iOS4.0開(kāi)始之后才能使用。GCD是一個(gè)替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術(shù)的很高效和強(qiáng)大的技術(shù)?,F(xiàn)在的iOS系統(tǒng)都升級(jí)到6了,所以不用擔(dān)心該技術(shù)不能使用。
介紹完這三種多線(xiàn)程編程方式,我們這篇先介紹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è)是類(lèi)方法
[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 :線(xiàn)程執(zhí)行的方法,這個(gè)selector只能有一個(gè)參數(shù),而且不能有返回值。
target :selector消息發(fā)送的對(duì)象
argument:傳輸給target的唯一參數(shù),也可以是nil
第一種方式會(huì)直接創(chuàng)建線(xiàn)程并且開(kāi)始運(yùn)行線(xiàn)程,第二種方式是先創(chuàng)建線(xiàn)程對(duì)象,然后再運(yùn)行線(xiàn)程操作,在運(yùn)行線(xiàn)程操作前可以設(shè)置線(xiàn)程的優(yōu)先級(jí)等線(xiàn)程信息
2.3 PS:不顯式創(chuàng)建線(xiàn)程的方法:
用NSObject的類(lèi)方法 performSelectorInBackground:withObject: 創(chuàng)建一個(gè)線(xiàn)程:
[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線(xiàn)程間通訊
線(xiàn)程下載完圖片后怎么通知主線(xiàn)程更新界面呢?
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
performSelectorOnMainThread是NSObject的方法,除了可以更新主線(xiàn)程的數(shù)據(jù)外,還可以更新其他線(xiàn)程的比如:
用:performSelector:onThread:withObject:waitUntilDone:
運(yùn)行下載圖片:
圖片下載下來(lái)了。
2.3 線(xiàn)程同步
我們演示一個(gè)經(jīng)典的賣(mài)票的例子來(lái)講NSThread的線(xiàn)程同步:
.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];
// 鎖對(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];
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,線(xiàn)程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
// [ticketsCondition unlock];
}
}
如果沒(méi)有線(xiàn)程同步的lock,賣(mài)票數(shù)可能是-1.加上lock之后線(xiàn)程同步保證了數(shù)據(jù)的正確性。
上面例子我使用了兩種鎖,一種NSCondition ,一種是:NSLock。 NSCondition我已經(jīng)注釋了。
線(xiàn)程的順序執(zhí)行
他們都可以通過(guò)
[ticketsCondition signal]; 發(fā)送信號(hào)的方式,在一個(gè)線(xiàn)程喚醒另外一個(gè)線(xiàn)程的等待。
比如:
[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];
// 鎖對(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];
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,線(xiàn)程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
[ticketsCondition unlock];
}
}
wait是等待,我加了一個(gè) 線(xiàn)程3 去喚醒其他兩個(gè)線(xiàn)程鎖中的wait
其他同步
我們可以使用指令 @synchronized 來(lái)簡(jiǎn)化 NSLock的使用,這樣我們就不必顯示編寫(xiě)創(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
著作權(quán)聲明:本文由http://blog.csdn.net/totogo2010/原創(chuàng),歡迎轉(zhuǎn)載分享。請(qǐng)尊重作者勞動(dòng),轉(zhuǎn)載時(shí)保留該聲明和作者博客鏈接,謝謝!
《iOS多線(xiàn)程編程之NSThread的使用》
介紹三種多線(xiàn)程編程和NSThread的使用,這篇介紹NSOperation的使用。
使用 NSOperation的方式有兩種,
一種是用定義好的兩個(gè)子類(lèi):
NSInvocationOperation 和 NSBlockOperation。
另一種是繼承NSOperation
如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一樣,NSOperation也是設(shè)計(jì)用來(lái)擴(kuò)展的,只需繼承重寫(xiě)NSOperation的一個(gè)方法main。相當(dāng)與java 中Runnalbe的Run方法。然后把NSOperation子類(lèi)的對(duì)象放入NSOperationQueue隊(duì)列中,該隊(duì)列就會(huì)啟動(dòng)并開(kāi)始處理它。
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è)后臺(tái)線(xiàn)程,并且放到NSOperationQueue中。后臺(tái)線(xiàn)程執(zhí)行downloadImage方法。
downloadImage 方法處理下載圖片的邏輯。下載完成后用performSelectorOnMainThread執(zhí)行主線(xiàn)程updateUI方法。
updateUI 并把下載的圖片顯示到圖片控件中。
運(yùn)行可以看到下載圖片顯示在界面上。
第二種方式繼承NSOperation
在.m文件中實(shí)現(xiàn)main方法,main方法編寫(xiě)要執(zhí)行的代碼即可。
如何控制線(xiàn)程池中的線(xiàn)程數(shù)?
隊(duì)列里可以加入很多個(gè)NSOperation, 可以把NSOperationQueue看作一個(gè)線(xiàn)程池,可往線(xiàn)程池中添加操作(NSOperation)到隊(duì)列中。線(xiàn)程池中的線(xiàn)程可看作消費(fèi)者,從隊(duì)列中取走操作,并執(zhí)行它。
通過(guò)下面的代碼設(shè)置:
[queue setMaxConcurrentOperationCount:5];
線(xiàn)程池中的線(xiàn)程數(shù),也就是并發(fā)操作數(shù)。默認(rèn)情況下是-1,-1表示沒(méi)有限制,這樣會(huì)同時(shí)運(yùn)行隊(duì)列中的全部的操作。
介紹:
Grand Central Dispatch 簡(jiǎn)稱(chēng)(GCD)是蘋(píng)果公司開(kāi)發(fā)的技術(shù),以?xún)?yōu)化的應(yīng)用程序支持多核心處理器和其他的對(duì)稱(chēng)多處理系統(tǒng)的系統(tǒng)。這建立在任務(wù)并行執(zhí)行的線(xiàn)程池模式的基礎(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的底層依然是用線(xiàn)程實(shí)現(xiàn),不過(guò)這樣可以讓程序員不用關(guān)注實(shí)現(xiàn)的細(xì)節(jié)。
GCD中的FIFO隊(duì)列稱(chēng)為dispatch queue,它可以保證先進(jìn)來(lái)的任務(wù)先得到執(zhí)行
dispatch queue分為下面三種:
Serial
又稱(chēng)為private dispatch queues,同時(shí)只執(zhí)行一個(gè)任務(wù)。Serial queue通常用于同步訪(fǎng)問(wèn)特定的資源或數(shù)據(jù)。當(dāng)你創(chuàng)建多個(gè)Serial queue時(shí),雖然它們各自是同步執(zhí)行的,但Serial queue與Serial queue之間是并發(fā)執(zhí)行的。
Concurrent
又稱(chēng)為global dispatch queue,可以并發(fā)地執(zhí)行多個(gè)任務(wù),但是執(zhí)行完成的順序是隨機(jī)的。
Main dispatch queue
它是全局可用的serial queue,它是在應(yīng)用程序主線(xiàn)程上執(zhí)行任務(wù)的。
我們看看dispatch queue如何使用
1、常用的方法dispatch_async
為了避免界面在處理耗時(shí)的操作時(shí)卡死,比如讀取網(wǎng)絡(luò)數(shù)據(jù),IO,數(shù)據(jù)庫(kù)讀寫(xiě)等,我們會(huì)在另外一個(gè)線(xiàn)程中處理這些操作,然后通知主線(xiàn)程更新界面。
用GCD實(shí)現(xiàn)這個(gè)流程的操作比前面介紹的NSThread NSOperation的方法都要簡(jiǎn)單。代碼框架結(jié)構(gòu)如下:
[cpp] view plaincopy
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗時(shí)的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
});
});
如果這樣還不清晰的話(huà),那我們還是用上兩篇博客中的下載圖片為例子,代碼如下:
[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簡(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ì)列,如下:
[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ù)的對(duì)象,但是以上兩個(gè)都是全局的隊(duì)列,不用retain或release。
2、dispatch_group_async的使用
dispatch_group_async可以實(shí)現(xiàn)監(jiān)聽(tīng)一組任務(wù)是否完成,完成后得到通知執(zhí)行其他的操作。這個(gè)方法很有用,比如你執(zhí)行三個(gè)下載任務(wù),當(dāng)三個(gè)任務(wù)都下載完成后你才通知界面說(shuō)完成的了。下面是一段例子代碼:
[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í)行完成之后才會(huì)執(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
請(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
前兩篇多線(xiàn)程博文:iOS多線(xiàn)程編程之NSThread的使用
iOS多線(xiàn)程編程之NSOperation和NSOperationQueue的使用
著作權(quán)聲明:本文由http://blog.csdn.net/totogo2010/原創(chuàng),歡迎轉(zhuǎn)載分享。請(qǐng)尊重作者勞動(dòng),轉(zhuǎn)載時(shí)保留該聲明和作者博客鏈接,謝謝!