iOS多線程 之NSThread的使用
1、簡介:
1.1 iOS有三種多線程編程的技術锚烦,分別是:
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 三種方式的有缺點介紹:
NSThread:
優(yōu)點:NSThread 比其他兩個輕量級
缺點:需要自己管理線程的生命周期茧痕,線程同步野来。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷
NSThread實現(xiàn)的技術有下面三種:
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 技術。
Cocoa operation
優(yōu)點:不需要關心線程管理踪旷,數(shù)據(jù)同步的事情曼氛,可以把精力放在自己需要執(zhí)行的操作上豁辉。
Cocoa operation相關的類是NSOperation ,NSOperationQueue舀患。
NSOperation是個抽象類徽级,使用它必須用它的子類,可以實現(xiàn)它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation聊浅。
創(chuàng)建NSOperation子類的對象餐抢,把對象添加到NSOperationQueue隊列里執(zhí)行。
GCD
Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的解決方法低匙。在iOS4.0開始之后才能使用旷痕。GCD是一個替代諸如NSThread, NSOperationQueue, NSInvocationOperation等技術的很高效和強大的技術。現(xiàn)在的iOS系統(tǒng)都升級到6了顽冶,所以不用擔心該技術不能使用欺抗。
介紹完這三種多線程編程方式,我們這篇先介紹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
第一個是實例方法,第二個是類方法
[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í)行的方法,這個selector只能有一個參數(shù)拱燃,而且不能有返回值秉溉。
target :selector消息發(fā)送的對象
argument:傳輸給target的唯一參數(shù),也可以是nil
第一種方式會直接創(chuàng)建線程并且開始運行線程碗誉,第二種方式是先創(chuàng)建線程對象召嘶,然后再運行線程操作,在運行線程操作前可以設置線程的優(yōu)先級等線程信息
2.3 PS:不顯式創(chuàng)建線程的方法:
用NSObject的類方法 performSelectorInBackground:withObject: 創(chuàng)建一個線程:
[Object performSelectorInBackground:@selector(doSomething)
withObject:nil];
2.4 下載圖片的例子:
2.4.1 新建singeView app
新建項目哮缺,并在xib文件上放置一個imageView控件弄跌。按住control鍵拖到viewControll
er.h文件中創(chuàng)建imageView IBOutlet
ViewController.m中實現(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:
運行下載圖片:
圖片下載下來了。
2.3 線程同步
我們演示一個經(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(@"當前票數(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ā)送信號的方式红柱,在一個線程喚醒另外一個線程的等待承匣。
比如:
[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(@"當前票數(shù)是:%d,售出:%d,線程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
[ticketsCondition unlock];
}
}
wait是等待蓖乘,我加了一個 線程3 去喚醒其他兩個線程鎖中的wait
其他同步
我們可以使用指令 @synchronized 來簡化 NSLock的使用,這樣我們就不必顯示編寫創(chuàng)建NSLock,加鎖并解鎖相關代碼韧骗。
(void)doSomeThing:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected
by the @synchronized directive.
}
}
還有其他的一些鎖對象嘉抒,比如:循環(huán)鎖NSRecursiveLock,條件鎖NSConditionLock袍暴,分布式鎖NSDistributedLock等等,可以自己看官方文檔學習
一種是用定義好的兩個子類:
NSInvocationOperation 和 NSBlockOperation众眨。
另一種是繼承NSOperation
如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似容诬。和Java的Runnable一樣,NSOperation也是設計用來擴展的沿腰,只需繼承重寫NSOperation的一個方法main览徒。相當與java 中Runnalbe的Run方法。然后把NSOperation子類的對象放入NSOperationQueue隊列中颂龙,該隊列就會啟動并開始處理它习蓬。
NSInvocationOperation例子:
實現(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建了一個后臺線程,并且放到NSOperationQueue中措嵌。后臺線程執(zhí)行downloadImage方法躲叼。
downloadImage 方法處理下載圖片的邏輯。下載完成后用performSelectorOnMainThread執(zhí)行主線程updateUI方法企巢。
updateUI 并把下載的圖片顯示到圖片控件中枫慷。
運行可以看到下載圖片顯示在界面上。
第二種方式繼承NSOperation
在.m文件中實現(xiàn)main方法浪规,main方法編寫要執(zhí)行的代碼即可或听。
如何控制線程池中的線程數(shù)?
隊列里可以加入很多個NSOperation, 可以把NSOperationQueue看作一個線程池笋婿,可往線程池中添加操作(NSOperation)到隊列中誉裆。線程池中的線程可看作消費者,從隊列中取走操作缸濒,并執(zhí)行它足丢。
通過下面的代碼設置:
[queue setMaxConcurrentOperationCount:5];
線程池中的線程數(shù),也就是并發(fā)操作數(shù)庇配。默認情況下是-1斩跌,-1表示沒有限制,這樣會同時運行隊列中的全部的操作讨永。
介紹:
Grand Central Dispatch 簡稱(GCD)是蘋果公司開發(fā)的技術滔驶,以優(yōu)化的應用程序支持多核心處理器和其他的對稱多處理系統(tǒng)的系統(tǒng)。這建立在任務并行執(zhí)行的線程池模式的基礎上的卿闹。它首次發(fā)布在Mac OS X 10.6 揭糕,iOS 4及以上也可用萝快。
設計:
GCD的工作原理是:讓程序平行排隊的特定任務,根據(jù)可用的處理資源著角,安排他們在任何可用的處理器核心上執(zhí)行任務揪漩。
一個任務可以是一個函數(shù)(function)或者是一個block。 GCD的底層依然是用線程實現(xiàn)吏口,不過這樣可以讓程序員不用關注實現(xiàn)的細節(jié)奄容。
GCD中的FIFO隊列稱為dispatch queue,它可以保證先進來的任務先得到執(zhí)行
dispatch queue分為下面三種:
Serial
又稱為private dispatch queues产徊,同時只執(zhí)行一個任務昂勒。Serial queue通常用于同步訪問特定的資源或數(shù)據(jù)。當你創(chuàng)建多個Serial queue時舟铜,雖然它們各自是同步執(zhí)行的戈盈,但Serial queue與Serial queue之間是并發(fā)執(zhí)行的。
Concurrent
又稱為global dispatch queue谆刨,可以并發(fā)地執(zhí)行多個任務塘娶,但是執(zhí)行完成的順序是隨機的。
Main dispatch queue
它是全局可用的serial queue痊夭,它是在應用程序主線程上執(zhí)行任務的刁岸。
我們看看dispatch queue如何使用
1、常用的方法dispatch_async
為了避免界面在處理耗時的操作時卡死她我,比如讀取網(wǎng)絡數(shù)據(jù)虹曙,IO,數(shù)據(jù)庫讀寫等,我們會在另外一個線程中處理這些操作鸦难,然后通知主線程更新界面根吁。
用GCD實現(xiàn)這個流程的操作比前面介紹的NSThread NSOperation的方法都要簡單。代碼框架結(jié)構如下:
[cpp] view plaincopy
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗時的操作
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;
});
}
});
運行顯示:
是不是代碼比NSThread NSOperation簡潔很多合蔽,而且GCD會自動根據(jù)任務在多核處理器上分配資源击敌,優(yōu)化程序。
系統(tǒng)給每一個應用程序提供了三個concurrent dispatch queues拴事。這三個并發(fā)調(diào)度隊列是全局的沃斤,它們只有優(yōu)先級的不同。因為是全局的刃宵,我們不需要去創(chuàng)建衡瓶。我們只需要通過使用函數(shù)dispath_get_global_queue去得到隊列,如下:
[cpp] view plaincopy
dispatch_queue_t globalQ =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
這里也用到了系統(tǒng)默認就有一個串行隊列main_queue
[cpp] view plaincopy
dispatch_queue_t mainQ = dispatch_get_main_queue();
雖然dispatch queue是引用計數(shù)的對象牲证,但是以上兩個都是全局的隊列哮针,不用retain或release。
2、dispatch_group_async的使用
dispatch_group_async可以實現(xiàn)監(jiān)聽一組任務是否完成十厢,完成后得到通知執(zhí)行其他的操作等太。這個方法很有用,比如你執(zhí)行三個下載任務蛮放,當三個任務都下載完成后你才通知界面說完成的了缩抡。下面是一段例子代碼:
[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是異步的方法,運行后可以看到打印結(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
每個一秒打印一個包颁,當?shù)谌齻€任務執(zhí)行后瞻想,upadteUi被打印。
3娩嚼、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任務執(zhí)行結(jié)束后它才執(zhí)行蘑险,而且它后面的任務等它執(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í)行的時間,可以看到執(zhí)行的順序如上所述岳悟。
4漠其、dispatch_apply
執(zhí)行某個代碼片段N次。
dispatch_apply(5, globalQ, ^(size_t index) {
// 執(zhí)行5次
});