開發中為了更好的用戶體驗我們會用到多線程。
主要討論三中創建多線程的方法:NSThread,GCD,NSOperation 。
NSThread
從命名來看這是一個封裝好的類,它的生命周期需要我們手動管理。常用的創建方法
1、通過類方法創建并自動啟動:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
2、通過實例方法創建手動啟動:
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[newThread setName:@"threadName"];//自定義線程名稱
[newThread start];//啟動
[newThread cancel];//取消
關于NSThread我們實際開發中常用的方法是:
+ (NSThread *)currentThread;//獲取當前線程信息
+ (NSThread *)mainThread;//獲取主線程信息
+ (void)sleepForTimeInterval:(NSTimeInterval)time;//調試時用的睡眠
GCD
GCD為Grand Central Dispatch首字母縮寫,是Apple開發的一個多核編程的解決方案。起源于Mac OS X 10.6,在iOS4.0被引入。GCD會自動管理線程的生命周期,采用C語言實現,通過Block將執行體傳入。
1、GCD中有三種隊列類型:
①the main queue:
獲取方式dispatch_queue_t mainQueue = dispatch_get_main_queue();提交到該隊列中的任務會在主線程執行,為一個串行隊列。
②the global queue:
獲取方式??? dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);全局隊列是并發隊列,并由整個進程共享,第一個參數為選擇隊列,參數可選為
#define DISPATCH_QUEUE_PRIORITY_HIGH 2//高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0//中,默認
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)//低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN//后臺
③the user defined queue:
獲取方式:第一個參數為名稱,debug時會顯示
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、應用:
①關于dispatch_async---添加任務到一個隊列并不等待任務完成,而是立即繼續其他任務。
//首先相對當前語句所在的線程來說是異步提交,將任務提交到default級別的全局隊列中返回,block等待default隊列FIFO順序執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongTask];//在default隊列中執行完,此時不會繼續往下執行,除非當前語句塊執行完成
dispatch_async(dispatch_get_main_queue(), ^{//異步提交任務到main_queue
[textField setStringValue:@"Done doing something long and involved"];//在main中執行當前語句塊
});
});
②關于dispatch_sync---添加任務到一個隊列并等待直到任務完成,用于等待提交任務的結果,才能繼續執行下面的代碼塊的情況。
dispatch_sync一般應用在并發隊列:這才是做同步工作的好選擇。
在主線程中執行如下代碼塊:
//首先相對當前語句所在的線程來說是同步提交,將任務提交到main queue中,等待main queue執行該block-----這里會造成阻塞,因為sync已經阻塞當前線程,等待block在所提交的線程執行完成后才放開阻塞的線程,即A被阻塞等待任務執行完,任務又等A的調度執行。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog("sync - %@", NSThread.currentThread());
});
③關于dispatch_group
演示一個應用:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);//獲得一個異步隊列
dispatch_group_t group = dispatch_group_create();//新建一個group隊列
for(idobj in array){//for循環遍歷 每次異步方式提交任務block到queue,
dispatch_group_async(group, queue, ^{
[selfdoSomethingIntensiveWithbj];
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//group進入永久阻塞等待,直到之前任務完成
dispatch_release(group);
[selfdoSomethingWith:array];//group完成后,執行此
優化:將最后的任務放到異步線程中執行
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
for(idobj in array){//近乎平行執行
dispatch_group_async(group, queue, ^{
[selfdoSomethingIntensiveWithbj];
});
}
//之前任務完成后會被notify通知執行
dispatch_group_notify(group, queue, ^{
[selfdoSomethingWith:array];
});
dispatch_release(group);
④關于dispatch_apply。
同步執行dispath_apply代碼塊,調用單一block多次,并平行運算,然后等待所有運算結束,需保證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];//最后執行
⑤關于dispatch_barrier。在并發隊列中扮演一個串行式的瓶頸作用。
能夠確保提交的block在被執行的特定時間上是指定隊列上唯一被執行的條目!由于隊列是FIFO的,換句話說所有先于該barrier block的條目一定能再這個block執行前完成。等完成該block后隊列恢復默認狀態。
應用--讀寫問題:
線程不能保證安全
- (void)addPhoto:(Photo *)photo
{
if (photo) {
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
[self postContentAddedNotification];
}
修改成一個線程安全的寫操作
-(void)addPhoto:(Photo *)photo
{
if (photo) { // 1判斷非空
dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2 barrier方式異步提交代碼塊到queue中
[_photosArray addObject:photo]; // 3等之前提交的都執行完后,只有當前代碼塊在queue中執行,barrier提交確保線程安全的!
dispatch_async(dispatch_get_main_queue(), ^{ // 4通知
[self postContentAddedNotification];
});
});
}
}
讀操作
不能提供任何保護來對抗當一個線程調用讀方法的同時另一個線程調用寫方法
- (NSArray *)photos
{
return [NSArray arrayWithArray:_photosArray];
}
修改為:
- (NSArray *)photos
{
__block NSArray *array; // 1
dispatch_sync(self.concurrentPhotoQueue, ^{ // 2同步提交等待,block代碼塊在queue中執行那個完成,因為寫操作是barrier提交保證了執行時只有寫操作自己在queue中!
array = [NSArray arrayWithArray:_photosArray]; // 3等待queue調度執行代碼塊
});
return array;
}
⑥dispatch_once,常用于創建線程安全的單例。
+ (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信號量 阻塞方式的一種,為0等待,否則減一繼續
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);//初始化為1,保證不會一開始被阻塞。
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計數大于等于1.計數-1,返回,程序繼續向下運行。如果計數為0,則等待
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
// Increment the counting semaphore.
dispatch_semaphore_signal(semaphore);//加1});
}
NSOperation And NSOperationQueue
1、NSOperation? 是一個抽象類,首先繼承NSOperation? 重寫main函數 在main中創建一個autoreleasepool 在autoreleasepool中添加代碼。
①start開始:通常不重載該方法,如果重載,必須關注像isExecuting, isFinished, isConcurrent, and isReady這些屬性。
將一個operation添加到NSOperationQueue隊列后,隊列會自動調用該operation的start方法,執行完一些準備工作后,執行main函數。
但是,如果手動觸發start方法,并沒有添加到NSOperationQueue隊列中,該operation將會在主線程中運行。
②dependency依賴:兩個operation之間可以建立依賴關系。
NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---new1-:%@",[NSThread currentThread]);
}];
NSBlockOperation *storeOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---new1-:%@",[NSThread currentThread]);
}];
[storeOperation addDependency:downloadOperation];//store依賴download 等到download操作的isFinished 為YES時,才開始執行 [storeOperation removeDependency:downloadOperation];//移除依賴關系
③priority優先級: 當你增加一個操作到隊列,在調用他們的“start”之前,NSOperationQueue會查找所有的操作,優先級高的操作優先執行。同級的操作按照提交到隊列的順序執行(FIFO).
[downloadOperation setQueuePriority:NSOperationQueuePriorityHigh];//設置優先級
④completion block完成后的響應block
[blockOperation setCompletionBlock:^{
//code不一定在主線程中
}];
2、NSOperationQueue? 是一個隊列。一個Queue隊列中可以有多個線程,不同的線程并發的執行。隊列中并發操作的數目小于等于所設置的最大運行數。要定期檢查(昂貴操作前后)isCancelled,確保盡快終止操作。
舉個小栗子:
@implementation myOperation
//一般不需要重載 如果重載,必須關注像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;//同時執行的最大并發數
for(int i = 0; i < 100;i++){
myOperation *operation = [[myOperation alloc] init];
[queue addOperation:operation];//不一定會立即觸發
}
關于GCD和NSOperation各有所長,以具體需求取舍吧。