開發(fā)中為了更好的用戶體驗(yàn)我們會(huì)用到多線程芬沉。
主要討論三中創(chuàng)建多線程的方法:NSThread,GCD阁猜,NSOperation 丸逸。
NSThread
從命名來看這是一個(gè)封裝好的類,它的生命周期需要我們手動(dòng)管理剃袍。常用的創(chuàng)建方法
1黄刚、通過類方法創(chuàng)建并自動(dòng)啟動(dòng):
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
2、通過實(shí)例方法創(chuàng)建手動(dòng)啟動(dòng):
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[newThread setName:@"threadName"];//自定義線程名稱
[newThread start];//啟動(dòng)
[newThread cancel];//取消
關(guān)于NSThread我們實(shí)際開發(fā)中常用的方法是:
+ (NSThread *)currentThread;//獲取當(dāng)前線程信息
+ (NSThread *)mainThread;//獲取主線程信息
+ (void)sleepForTimeInterval:(NSTimeInterval)time;//調(diào)試時(shí)用的睡眠
GCD
GCD為Grand Central Dispatch首字母縮寫民效,是Apple開發(fā)的一個(gè)多核編程的解決方案憔维。起源于Mac OS X 10.6,在iOS4.0被引入。GCD會(huì)自動(dòng)管理線程的生命周期畏邢,采用C語言實(shí)現(xiàn)业扒,通過Block將執(zhí)行體傳入。
1舒萎、GCD中有三種隊(duì)列類型:
①the main queue:
獲取方式dispatch_queue_t mainQueue = dispatch_get_main_queue();提交到該隊(duì)列中的任務(wù)會(huì)在主線程執(zhí)行程储,為一個(gè)串行隊(duì)列。
②the global queue:
獲取方式??? dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);全局隊(duì)列是并發(fā)隊(duì)列臂寝,并由整個(gè)進(jìn)程共享章鲤,第一個(gè)參數(shù)為選擇隊(duì)列,參數(shù)可選為
#define DISPATCH_QUEUE_PRIORITY_HIGH 2//高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0//中咆贬,默認(rèn)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)//低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN//后臺(tái)
③the user defined queue:
獲取方式:第一個(gè)參數(shù)為名稱败徊,debug時(shí)會(huì)顯示
dispatch_queue_t serialQueue =dispatch_queue_create("com.serial.queue",NULL);//串行
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);//串行
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);//并行
2、應(yīng)用:
①關(guān)于dispatch_async---添加任務(wù)到一個(gè)隊(duì)列并不等待任務(wù)完成素征,而是立即繼續(xù)其他任務(wù)集嵌。
//首先相對(duì)當(dāng)前語句所在的線程來說是異步提交,將任務(wù)提交到default級(jí)別的全局隊(duì)列中返回御毅,block等待default隊(duì)列FIFO順序執(zhí)行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongTask];//在default隊(duì)列中執(zhí)行完,此時(shí)不會(huì)繼續(xù)往下執(zhí)行根欧,除非當(dāng)前語句塊執(zhí)行完成
dispatch_async(dispatch_get_main_queue(), ^{//異步提交任務(wù)到main_queue
[textField setStringValue:@"Done doing something long and involved"];//在main中執(zhí)行當(dāng)前語句塊
});
});
②關(guān)于dispatch_sync---添加任務(wù)到一個(gè)隊(duì)列并等待直到任務(wù)完成,用于等待提交任務(wù)的結(jié)果,才能繼續(xù)執(zhí)行下面的代碼塊的情況端蛆。
dispatch_sync一般應(yīng)用在并發(fā)隊(duì)列:這才是做同步工作的好選擇凤粗。
在主線程中執(zhí)行如下代碼塊:
//首先相對(duì)當(dāng)前語句所在的線程來說是同步提交,將任務(wù)提交到main queue中今豆,等待main queue執(zhí)行該block-----這里會(huì)造成阻塞嫌拣,因?yàn)閟ync已經(jīng)阻塞當(dāng)前線程,等待block在所提交的線程執(zhí)行完成后才放開阻塞的線程呆躲,即A被阻塞等待任務(wù)執(zhí)行完异逐,任務(wù)又等A的調(diào)度執(zhí)行。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog("sync - %@", NSThread.currentThread());
});
③關(guān)于dispatch_group
演示一個(gè)應(yīng)用:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);//獲得一個(gè)異步隊(duì)列
dispatch_group_t group = dispatch_group_create();//新建一個(gè)group隊(duì)列
for(idobj in array){//for循環(huán)遍歷 每次異步方式提交任務(wù)block到queue插掂,
dispatch_group_async(group, queue, ^{
[selfdoSomethingIntensiveWithbj];
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//group進(jìn)入永久阻塞等待灰瞻,直到之前任務(wù)完成
dispatch_release(group);
[selfdoSomethingWith:array];//group完成后,執(zhí)行此
優(yōu)化:將最后的任務(wù)放到異步線程中執(zhí)行
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
for(idobj in array){//近乎平行執(zhí)行
dispatch_group_async(group, queue, ^{
[selfdoSomethingIntensiveWithbj];
});
}
//之前任務(wù)完成后會(huì)被notify通知執(zhí)行
dispatch_group_notify(group, queue, ^{
[selfdoSomethingWith:array];
});
dispatch_release(group);
④關(guān)于dispatch_apply辅甥。
同步執(zhí)行dispath_apply代碼塊酝润,調(diào)用單一block多次,并平行運(yùn)算璃弄,然后等待所有運(yùn)算結(jié)束要销,需保證block中線程安全
dispatch_apply(array.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
[NSThread sleepForTimeInterval:1];
NSLog(@"---%@",array[index]);
});
[self say];//最后執(zhí)行
⑤關(guān)于dispatch_barrier。在并發(fā)隊(duì)列中扮演一個(gè)串行式的瓶頸作用夏块。
能夠確保提交的block在被執(zhí)行的特定時(shí)間上是指定隊(duì)列上唯一被執(zhí)行的條目疏咐!由于隊(duì)列是FIFO的,換句話說所有先于該barrier block的條目一定能再這個(gè)block執(zhí)行前完成脐供。等完成該block后隊(duì)列恢復(fù)默認(rèn)狀態(tài)凳鬓。
應(yīng)用--讀寫問題:
線程不能保證安全
- (void)addPhoto:(Photo *)photo
{
if (photo) {
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
[self postContentAddedNotification];
}
修改成一個(gè)線程安全的寫操作
-(void)addPhoto:(Photo *)photo
{
if (photo) { // 1判斷非空
dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2 barrier方式異步提交代碼塊到queue中
[_photosArray addObject:photo]; // 3等之前提交的都執(zhí)行完后,只有當(dāng)前代碼塊在queue中執(zhí)行患民,barrier提交確保線程安全的缩举!
dispatch_async(dispatch_get_main_queue(), ^{ // 4通知
[self postContentAddedNotification];
});
});
}
}
讀操作
不能提供任何保護(hù)來對(duì)抗當(dāng)一個(gè)線程調(diào)用讀方法的同時(shí)另一個(gè)線程調(diào)用寫方法
- (NSArray *)photos
{
return [NSArray arrayWithArray:_photosArray];
}
修改為:
- (NSArray *)photos
{
__block NSArray *array; // 1
dispatch_sync(self.concurrentPhotoQueue, ^{ // 2同步提交等待,block代碼塊在queue中執(zhí)行那個(gè)完成匹颤,因?yàn)閷懖僮魇莃arrier提交保證了執(zhí)行時(shí)只有寫操作自己在queue中仅孩!
array = [NSArray arrayWithArray:_photosArray]; // 3等待queue調(diào)度執(zhí)行代碼塊
});
return array;
}
⑥dispatch_once,常用于創(chuàng)建線程安全的單例印蓖。
+ (TransHistoryInstance *)sharedInstance
{
static TransHistoryInstance *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
⑦dispatch_after? 用于延遲操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//code to be executed after a specified delay
});
⑧ dispatch_semaphore信號(hào)量 阻塞方式的一種辽慕,為0等待,否則減一繼續(xù)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);//初始化為1,保證不會(huì)一開始被阻塞赦肃。
NSMutableArray *array = [NSMutableArray array];
for (int index = 0; index < 100000; index++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(){
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//如果semaphore計(jì)數(shù)大于等于1.計(jì)數(shù)-1溅蛉,返回公浪,程序繼續(xù)向下運(yùn)行。如果計(jì)數(shù)為0船侧,則等待
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
// Increment the counting semaphore.
dispatch_semaphore_signal(semaphore);//加1});
}
NSOperation And NSOperationQueue
1欠气、NSOperation? 是一個(gè)抽象類,首先繼承NSOperation? 重寫main函數(shù) 在main中創(chuàng)建一個(gè)autoreleasepool 在autoreleasepool中添加代碼镜撩。
①start開始:通常不重載該方法预柒,如果重載,必須關(guān)注像isExecuting, isFinished, isConcurrent, and isReady這些屬性袁梗。
將一個(gè)operation添加到NSOperationQueue隊(duì)列后宜鸯,隊(duì)列會(huì)自動(dòng)調(diào)用該operation的start方法,執(zhí)行完一些準(zhǔn)備工作后遮怜,執(zhí)行main函數(shù)淋袖。
但是,如果手動(dòng)觸發(fā)start方法锯梁,并沒有添加到NSOperationQueue隊(duì)列中适贸,該operation將會(huì)在主線程中運(yùn)行。
②dependency依賴:兩個(gè)operation之間可以建立依賴關(guān)系涝桅。
NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---new1-:%@",[NSThread currentThread]);
}];
NSBlockOperation *storeOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---new1-:%@",[NSThread currentThread]);
}];
[storeOperation addDependency:downloadOperation];//store依賴download 等到download操作的isFinished 為YES時(shí)拜姿,才開始執(zhí)行 [storeOperation removeDependency:downloadOperation];//移除依賴關(guān)系
③priority優(yōu)先級(jí): 當(dāng)你增加一個(gè)操作到隊(duì)列,在調(diào)用他們的“start”之前冯遂,NSOperationQueue會(huì)查找所有的操作蕊肥,優(yōu)先級(jí)高的操作優(yōu)先執(zhí)行。同級(jí)的操作按照提交到隊(duì)列的順序執(zhí)行(FIFO).
[downloadOperation setQueuePriority:NSOperationQueuePriorityHigh];//設(shè)置優(yōu)先級(jí)
④completion block完成后的響應(yīng)block
[blockOperation setCompletionBlock:^{
//code不一定在主線程中
}];
2蛤肌、NSOperationQueue? 是一個(gè)隊(duì)列壁却。一個(gè)Queue隊(duì)列中可以有多個(gè)線程,不同的線程并發(fā)的執(zhí)行裸准。隊(duì)列中并發(fā)操作的數(shù)目小于等于所設(shè)置的最大運(yùn)行數(shù)展东。要定期檢查(昂貴操作前后)isCancelled,確保盡快終止操作炒俱。
舉個(gè)小栗子:
@implementation myOperation
//一般不需要重載 如果重載盐肃,必須關(guān)注像isExecuting, isFinished, isConcurrent, and isReady這些屬性。
//- (void)start{
//
//}
- (void)main
{
@autoreleasepool {
NSLog(@"isMainThread-%d",[NSThread isMainThread]);
if (self.isCancelled)
return;
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:@"http://images.freeimages.com/images/previews/3a2/fall-in-ontario-1056162.jpg"]];
if (self.isCancelled) {
imageData = nil;
return;
}
// do something about imageData
if (self.isCancelled)
return;
// report to somebody on main thread
}
}
使用:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"my Queue";
queue.maxConcurrentOperationCount = 3;//同時(shí)執(zhí)行的最大并發(fā)數(shù)
for(int i = 0; i < 100;i++){
myOperation *operation = [[myOperation alloc] init];
[queue addOperation:operation];//不一定會(huì)立即觸發(fā)
}
關(guān)于GCD和NSOperation各有所長(zhǎng)权悟,以具體需求取舍吧砸王。